μœˆλ„μš°μ™€ λ¦¬λˆ…μŠ€μ˜ λ©€ν‹°ν”„λ‘œμ„Έμ‹± λ™μž‘ 방식 차이

2025. 1. 20. 13:38Β·πŸ’» 개발/πŸ–₯️ 운영체제

 

 

λ“€μ–΄κ°€λ©°

μž‘λ…„μ— μ‹€λ¬΄μ—μ„œ νŒŒμ΄μ¬μ„ μ‚¬μš©ν•΄ λ©€ν‹°ν”„λ‘œμ„Έμ‹± μž‘μ—…μ„ μˆ˜ν–‰ν•˜λ©΄μ„œ λ¬Έμ œκ°€ λ°œμƒν•œ 적이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

νŠΉμ • μž‘μ—…μ—μ„œ SQLAlchemy(파이썬 ORM 라이브러리)의 DB 컀λ„₯μ…˜ 풀을 μ‚¬μš©ν•΄ 데이터λ₯Ό INSERTν•˜λŠ” 둜직이 μžˆμ—ˆλŠ”λ°, 둜컬 개발 ν™˜κ²½(μœˆλ„μš°)μ—μ„œλŠ” μ•„λ¬΄λŸ° λ¬Έμ œκ°€ μ—†μ—ˆμŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ λ™μΌν•œ μ½”λ“œλ₯Ό 사내 개발 μ„œλ²„(λ¦¬λˆ…μŠ€)에 λ°°ν¬ν–ˆμ„ λ•Œ λ‹€μŒκ³Ό 같은 μ—λŸ¬ λ‘œκ·Έκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.

 

2024-12-21 17:57:00,471 [ERROR   ] <root> [Process-21][Thread-140372676175552]: write_module:76:  Error during bulk insert: ORA-01001: invalid cursor

ORA-03106: fatal two-task communication protocol error

DPI-1080: connection was closed by ORA-3135

 

μ—λŸ¬λ₯Ό μ‚΄νŽ΄λ³΄λ©΄, μ»€μ„œκ°€ 잘λͺ»λ˜κ±°λ‚˜ 컀λ„₯μ…˜μ΄ λ‹«ν˜”μŒμ„ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. 특히 SQLAlchemyκ°€ μ‚¬μš©ν•˜λŠ” DB 컀λ„₯μ…˜ 풀이 λ¦¬λˆ…μŠ€ ν™˜κ²½μ—μ„œ μΆ©λŒμ„ μΌμœΌμΌ°λ‹€λŠ” 점이 문제의 ν•΅μ‹¬μœΌλ‘œ λ³΄μ˜€μŠ΅λ‹ˆλ‹€.

 

κ·Έλ ‡λ‹€λ©΄ μ™œ 같은 μ½”λ“œκ°€ OS에 따라 λ‹€λ₯΄κ²Œ λ™μž‘ν–ˆλ˜ κ±ΈκΉŒμš”?

 

 

 

 

 

SQLAlchemy 컀λ„₯μ…˜ ν’€κ³Ό λ©€ν‹°ν”„λ‘œμ„Έμ‹±

이에 λŒ€ν•œ 힌트λ₯Ό SQLAlchemy 곡식 λ¬Έμ„œμ—μ„œ μ°Ύμ•„λ³Ό 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

https://docs.sqlalchemy.org/en/20/core/pooling.html#pooling-multiprocessing

 

Connection Pooling — SQLAlchemy 2.0 Documentation

Connection Pooling A connection pool is a standard technique used to maintain long running connections in memory for efficient re-use, as well as to provide management for the total number of connections an application might use simultaneously. Particularl

docs.sqlalchemy.org

 

It’s critical that when using a connection pool, and by extension when using an Engine created via create_engine(), that the pooled connections are not shared to a forked process.
(...μ€‘λž΅)
The SQLAlchemy Engine object refers to a connection pool of existing database connections. So when this object is replicated to a child process, the goal is to ensure that no database connections are carried over.

 

 

곡식 λ¬Έμ„œμ—μ„œ SQLAlchemy의 엔진은 fork된 ν”„λ‘œμ„ΈμŠ€μ— κΈ°μ‘΄ λ°μ΄ν„°λ² μ΄μŠ€μ˜ 컀λ„₯μ…˜ 풀을 μ°Έμ‘°ν•œλ‹€κ³  μ„€λͺ…λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 즉, μžμ‹ ν”„λ‘œμ„ΈμŠ€λ₯Ό 생성할 λ•Œ fork λ°©μ‹μœΌλ‘œ λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€λ₯Ό λ³΅μ‚¬ν•˜κΈ° λ•Œλ¬Έμ— λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€κ°€ κ°€μ§€κ³  μžˆλŠ” 컀λ„₯μ…˜ 풀을 κ³΅μœ ν•˜κ²Œ λ©λ‹ˆλ‹€. 이둜 인해 μžμ‹ ν”„λ‘œμ„ΈμŠ€μ—μ„œ λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ˜ λ¦¬μ†ŒμŠ€λ₯Ό μ‚¬μš©ν•˜λ €λ‹€ 좩돌이 λ°œμƒν•©λ‹ˆλ‹€.

 

μ΄λŸ¬ν•œ λ©€ν‹°ν”„λ‘œμ„Έμ‹± ν™˜κ²½μ—μ„œ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λͺ‡ κ°€μ§€ 방법을 μ œμ‹œν•˜κ³  μžˆλŠ”λ° κ·Έ 쀑 engine.dispose(close=False)λ₯Ό μ‚¬μš©ν•˜λŠ” 것을 ꢌμž₯ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

μ•„λž˜ μ˜ˆμ‹œ μ½”λ“œμ™€ 같이 μ—”μ§„ μ΄ˆκΈ°ν™” μ‹œ Engine.dispose.close의 λ§€κ°œλ³€μˆ˜ 값에 Falseλ₯Ό μ „λ‹¬ν•˜κ²Œ 되면, μžμ‹ ν”„λ‘œμ„ΈμŠ€κ°€ λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ˜ 컀λ„₯μ…˜ 풀을 κ±΄λ“œλ¦¬μ§€ μ•Šκ³  μƒˆλ‘œμš΄ 컀λ„₯μ…˜μ„ μ‹œμž‘ν•©λ‹ˆλ‹€.

from multiprocessing import Pool

engine = create_engine("mysql+mysqldb://user:pass@host/dbname")


def run_in_process(some_data_record):
    with engine.connect() as conn:
        conn.execute(text("..."))


def initializer():
    """ensure the parent proc's database connections are not touched
    in the new connection pool"""
    engine.dispose(close=False)


with Pool(10, initializer=initializer) as p:
    p.map(run_in_process, data)

 

μ½”λ“œ μ„€λͺ…

  1. engine.dispose(close=False)
    • μžμ‹ ν”„λ‘œμ„ΈμŠ€μ—μ„œ μƒˆλ‘œμš΄ 컀λ„₯μ…˜ 풀을 μƒμ„±ν•˜λ„λ‘ κ°•μ œν•©λ‹ˆλ‹€.
    • close=FalseλŠ” κΈ°μ‘΄ 연결을 λ‹«μ§€ μ•Šκ³  μƒˆ 컀λ„₯μ…˜λ§Œ μƒμ„±ν•©λ‹ˆλ‹€.
  2. initializer ν•¨μˆ˜
    • initializerλ₯Ό 톡해 각 μžμ‹ ν”„λ‘œμ„ΈμŠ€μ˜ μ΄ˆκΈ°ν™” μ‹œ μ—”μ§„ λ¦¬μ†ŒμŠ€λ₯Ό μ •λ¦¬ν•©λ‹ˆλ‹€.
  3. Pool μƒμ„± μ‹œ μ΄ˆκΈ°ν™” ν•¨μˆ˜ μ§€μ •
    • multiprocessing.Poolμ—μ„œ initializerλ₯Ό μ§€μ •ν•˜λ©΄ μžμ‹ ν”„λ‘œμ„ΈμŠ€ 생성 μ‹œ μžλ™μœΌλ‘œ ν˜ΈμΆœλ©λ‹ˆλ‹€.

 

 

 

 

 

μœˆλ„μš°μ™€ λ¦¬λˆ…μŠ€μ˜ λ©€ν‹°ν”„λ‘œμ„Έμ‹± 차이

κ·Έλ ‡λ‹€λ©΄ μœˆλ„μš°μ—μ„œλŠ” μ™œ 이런 λ¬Έμ œκ°€ λ°œμƒν•˜μ§€ μ•Šμ•˜λ˜κ±ΈκΉŒμš”?

κ·Έ μ΄μœ λŠ” νŒŒμ΄μ¬μ—μ„œ λ©€ν‹°ν”„λ‘œμ„Έμ‹±μ„ μˆ˜ν–‰ν•˜λŠ” 방식이 μš΄μ˜μ²΄μ œμ— 따라 λ‹€λ₯΄κ²Œ λ™μž‘ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

 

  • μœˆλ„μš°: spawn() λ°©μ‹μ„ μ‚¬μš©ν•΄ μƒˆλ‘œμš΄ ν”„λ‘œμ„ΈμŠ€λ₯Ό λ…λ¦½μ μœΌλ‘œ μƒμ„±ν•©λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ˜ λ¦¬μ†ŒμŠ€λ₯Ό μƒˆλ‘œ μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€.
  • λ¦¬λˆ…μŠ€: fork() λ°©μ‹μ„ μ‚¬μš©ν•΄ λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€λ₯Ό λ³΅μ‚¬ν•©λ‹ˆλ‹€. 이둜 인해 λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ˜ λͺ¨λ“  λ¦¬μ†ŒμŠ€, 즉 SQLAlchemy의 컀λ„₯μ…˜ 풀도 κ³΅μœ λ©λ‹ˆλ‹€.

 

이 λ™μž‘ 차이λ₯Ό κ°„λ‹¨ν•œ μ½”λ“œλ‘œ 확인해 λ³΄κ² μŠ΅λ‹ˆλ‹€.

import multiprocessing as mp
import random

val = random.random()

def simple_func():
    print(val)


if __name__ == '__main__':
    print('Before multiprocessing: ')
    simple_func()
    print('After multiprocessing:')
    p = mp.Process(target=simple_func)
    p.start()
    p.join()

 

 

μœˆλ„μš° 좜λ ₯ κ²°κ³Ό

Before multiprocessing: 
0.7345451427940827
After multiprocessing:
0.23541224006190398

 

μœˆλ„μš°λŠ” μžμ‹ ν”„λ‘œμ„ΈμŠ€κ°€ μ™„μ „νžˆ 독립적인 μƒˆλ‘œμš΄ κ³΅κ°„μ—μ„œ μ‹œμž‘ν•˜λ―€λ‘œ, random.random()으둜 μƒμ„±λœ val 값을 λ³΅μ‚¬ν•˜μ§€ μ•Šκ³  μžμ‹ ν”„λ‘œμ„ΈμŠ€μ—μ„œ μƒˆλ‘œ μ΄ˆκΈ°ν™”λ©λ‹ˆλ‹€.

결과적으둜 λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ™€ μžμ‹ ν”„λ‘œμ„ΈμŠ€μ˜ val 값이 μ„œλ‘œ λ‹€λ¦…λ‹ˆλ‹€.

 

 

λ¦¬λˆ…μŠ€ 좜λ ₯ κ²°κ³Ό

Before multiprocessing:
0.19180520863603323
After multiprocessing:
0.19180520863603323

 

fork()둜 인해 λΆ€λͺ¨μ™€ μžμ‹μ΄ λ™μΌν•œ λ¦¬μ†ŒμŠ€λ₯Ό κ³΅μœ ν•©λ‹ˆλ‹€. λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ˜ λͺ¨λ“  λ©”λͺ¨λ¦¬ μƒνƒœλ₯Ό λ³΅μ‚¬ν•˜κΈ° λ•Œλ¬Έμ—, λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ—μ„œ μƒμ„±λœ val 값이 μžμ‹ ν”„λ‘œμ„ΈμŠ€μ—λ„ λ™μΌν•˜κ²Œ μ „λ‹¬λ©λ‹ˆλ‹€.

결과적으둜 λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ™€ μžμ‹ ν”„λ‘œμ„ΈμŠ€μ˜ val 값이 λ™μΌν•©λ‹ˆλ‹€.

 

SQLAlchemyμ—μ„œλ„ λ™μΌν•œ μ›λ¦¬λ‘œ 인해 λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ˜ 컀λ„₯μ…˜ 풀이 μžμ‹ ν”„λ‘œμ„ΈμŠ€μ— λ³΅μ‚¬λ˜λ©° OS 간에 차이가 λ°œμƒν–ˆλ˜ κ²ƒμž…λ‹ˆλ‹€.

 

 

 

 

 

 

κ²°κ³Ό μš”μ•½

  1. μœˆλ„μš°μ™€ λ¦¬λˆ…μŠ€μ˜ λ©€ν‹°ν”„λ‘œμ„Έμ‹± 방식은 λ‹€λ₯΄λ‹€.
    • μœˆλ„μš°λŠ” spawn() λ°©μ‹μ„ μ‚¬μš©ν•΄ λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ™€ λ…λ¦½λœ μƒˆλ‘œμš΄ ν”„λ‘œμ„ΈμŠ€λ₯Ό μƒμ„±ν•˜μ§€λ§Œ, λ¦¬λˆ…μŠ€λŠ” fork()λ°©μ‹μœΌλ‘œ λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€λ₯Ό 볡사해 λ¦¬μ†ŒμŠ€λ₯Ό κ³΅μœ ν•©λ‹ˆλ‹€.
    • 이 차이둜 인해 λ¦¬λˆ…μŠ€ ν™˜κ²½μ—μ„œλŠ” λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ˜ λ¦¬μ†ŒμŠ€κ°€ μžμ‹ ν”„λ‘œμ„ΈμŠ€μ—μ„œ μΆ©λŒμ„ μΌμœΌν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.
  2. SQLAlchemyλŠ” λ©€ν‹°ν”„λ‘œμ„Έμ‹± ν™˜κ²½μ„ μœ„ν•΄ dispose(close=False) 방식을 ꢌμž₯ν•œλ‹€.
    • engine.dispose(close=False)λ₯Ό μ‚¬μš©ν•΄ μžμ‹ ν”„λ‘œμ„ΈμŠ€μ—μ„œ μƒˆλ‘œμš΄ 컀λ„₯μ…˜ 풀을 μƒμ„±ν•¨μœΌλ‘œμ¨ 문제λ₯Ό ν•΄κ²°ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

 

 

μ°Έκ³ 

https://pythonforthelab.com/blog/differences-between-multiprocessing-windows-and-linux/

https://docs.sqlalchemy.org/en/20/core/pooling.html#pooling-multiprocessing

μ €μž‘μžν‘œμ‹œ

'πŸ’» 개발 > πŸ–₯️ 운영체제' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

ν”„λ‘œμ„ΈμŠ€(Process)λž€?  (0) 2022.12.12
μΈν„°λŸ½νŠΈλŠ” 무엇이며 μ–΄λ–€ 역할을 ν• κΉŒ?  (0) 2022.11.23
'πŸ’» 개발/πŸ–₯️ 운영체제' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€
  • ν”„λ‘œμ„ΈμŠ€(Process)λž€?
  • μΈν„°λŸ½νŠΈλŠ” 무엇이며 μ–΄λ–€ 역할을 ν• κΉŒ?
EastShine_
EastShine_
더 λ‚˜μ€ κ°œλ°œμžκ°€ 되기 μœ„ν•œ λ‚˜μ˜ 기둝 πŸ“
  • EastShine_
    개발.LOG πŸ’»
    EastShine_
  • 전체
    였늘
    μ–΄μ œ
  • 05-13 01:29
    • λΆ„λ₯˜ 전체보기 (27)
      • πŸ’» 개발 (21)
        • πŸ–₯️ 운영체제 (3)
        • 🌏 λ„€νŠΈμ›Œν¬ (0)
        • πŸ’Ύ Database (3)
        • πŸŽ› Java (0)
        • πŸ–² Javascript (0)
        • πŸ€ Spring (5)
        • 🎸 ETC (4)
        • πŸ“ˆ μ•Œκ³ λ¦¬μ¦˜ (3)
        • πŸ“– TIL (Today I Learned) (3)
      • 🏠 일상 (6)
        • πŸ““ 일상 일기 (6)
  • 인기 κΈ€

  • νƒœκ·Έ

    transactionaleventlistener
    회고
    λ™μ‹œμ„±μ²˜λ¦¬
    e-book pdf λ³€ν™˜
    μ•Œκ³ λ¦¬μ¦˜
    νŠΈλžœμž­μ…˜ 뢄리
    e-book pdf μΆ”μΆœ
    λŒ€κΈ°μ—΄
    μ½˜μ„œνŠΈμ˜ˆμ•½μ„œλΉ„μŠ€
    ν”„λ‘œκ·Έλž˜λ¨ΈμŠ€
    Python
    ν•­ν•΄ν”ŒλŸ¬μŠ€
    λ°±μ—”λ“œ
    낙관적락
    비관적락
    6κΈ°
    redis
    spring
    Whisper API
    μ½”λ”©ν…ŒμŠ€νŠΈ
  • 졜근 λŒ“κΈ€

  • 졜근 κΈ€

  • hELLOΒ· Designed Byμ •μƒμš°.v4.10.1
EastShine_
μœˆλ„μš°μ™€ λ¦¬λˆ…μŠ€μ˜ λ©€ν‹°ν”„λ‘œμ„Έμ‹± λ™μž‘ 방식 차이
μƒλ‹¨μœΌλ‘œ

ν‹°μŠ€ν† λ¦¬νˆ΄λ°”