(Django) Django에서 Thread를 다룰 때 주의할 점
💡한줄 요약
Django 스크립트에서 Thread를 사용하는 경우, Thread가 종료될 때 명시적으로 DB Connection을 닫아주는 것이 좋다.
Djagno에서 Thread를 사용할 때, DB Connection이 관리되는 방식
Django에서 DB에 접근하는 Thread를 사용하는 경우, Thread마다 새로운 DB Connection을 생성된다.
Django는 request가 종료될때, request_finished signal을 발생시켜 지난 DB Connection들을 모두 닫는다. ( 참고 - DB Connection을 관리하는 방법 )
즉, Thread에서 만들어진 DB Connnecion은 Thread를 실행시킨 메인 프로세스가 종료될 때 닫힌다.
import threading
from product.models import Product
def print_product(index):
print(f"{index} function call")
print(Product.objects.last().name)
thread_list = []
for i in range(30):
th = threading.Thread(target=fun, name="[th def {}]".format(i), args=(i,))
th.start()
thread_list.append(th)
django console에서 위의 함수를 실행해보면, 콘솔이 종료 될 때까지 커넥션이 닫히지 않고 남아있는 것을 확인할 수 있다.
MultiThread와 DB Connection
Thread Pool을 사용하는 경우, Connection을 재사용한다. ( Thread 자체가 재사용된다. 그래서 Connection도 재사용된다. )
import concurrent.futures
from video.models import Product
def print_product(index):
print(Product.objects.last())
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
for i in range(100):
future = executor.submit(print_product, (i))
그래서 위와 같이 함수를 100번 실행하더라도, 이 경우 max_workers 갯수만큼만 커넥션이 생성된다.
import concurrent.futures
from video.models import Product
def print_product(index):
print(Product.objects.last())
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
for i in range(100):
future = executor.submit(print_product, (i))
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
for i in range(100):
future = executor.submit(print_product, (i))
그러나 위와 같이 ThreadPoolExecutor를 각각 실행한 경우, 총 6개의 DB Coonection이 새로 생성된다.
DB Connection 종료 시점
이러한 원리 때문에 실행시간이 긴 스크립트에서 멀티쓰레드를 사용하면, 다량으로 만들어진 Connection이 닫히지 않고 대기하는 상황들이 발생한다.
그러면 DB Connection이 계속 점유되고, Connection 수가 급증하는 현상이 발생할 수 있다.
때문에 Thread가 다 사용되고 나면, Connection을 명시적으로 닫아주는 것이 좋다.
import concurrent.futures
from video.models import Product
from django import db
def print_product(index):
print(Product.objects.last())
db.connections.close_all()
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
for i in range(100):
future = executor.submit(print_product, (i))
참고
https://stackoverflow.com/questions/8242837/django-multiprocessing-and-database-connections