비동기 View (async def)를 활용한 I/O 바운드 작업 성능 향상
Django 3.1부터 지원되는 비동기 View를 활용하여 I/O 바운드 작업의 성능을 획기적으로 개선하는 방법
비동기가 필요한 이유
I/O 바운드 작업: 외부 API 호출, 데이터베이스 쿼리, 파일 읽기/쓰기 등 입출력 시스템의 응답 속도에 의해 제한되는 작업
동기 방식의 문제: I/O 작업 완료까지 서버 자원이 대기하여 전체 처리량 저하
비동기 방식의 장점: I/O 대기 시간 동안 다른 요청을 처리하여 제한된 자원으로 더 많은 동시 요청 처리 가능
async def View 기본 사용법
# 동기 View - 순차적 처리 (약 2초 소요)
class SyncApiView(APIView):
def get(self, request, *args, **kwargs):
weather_data = requests.get("https://api.weather.com/seoul").json()
restaurant_data = requests.get("https://api.restaurants.com/gangnam").json()
return Response({"weather": weather_data, "restaurants": restaurant_data})
# 비동기 View - 동시 처리 (약 1초 소요)
class AsyncApiView(APIView):
async def get(self, request, *args, **kwargs):
async with aiohttp.ClientSession() as session:
tasks = [
fetch_url(session, "https://api.weather.com/seoul"),
fetch_url(session, "https://api.restaurants.com/gangnam")
]
results = await asyncio.gather(*tasks)
return Response({"weather": results[0], "restaurants": results[1]})Django ORM과 비동기
Django 4.1부터 ORM의 대부분 작업이 비동기 지원
기존 메서드명 앞에
a가 붙은 비동기 메서드 사용:aget,acreate,aupdate,adelete비동기를 지원하는 DB 드라이버 필요: PostgreSQL용
psycopg3, MySQL용aiomysql
# 동기 ORM
user = User.objects.get(pk=1)
# 비동기 ORM
user = await User.objects.aget(pk=1)주요 주의사항
"Async All the Way" 원칙
비동기 함수 안에서 동기 I/O 함수를 직접 호출하면 이벤트 루프 차단
모든 I/O 관련 라이브러리를 비동기 지원 버전으로 교체 필요
sync_to_async 어댑터
from asgiref.sync import sync_to_async
async_send_sms = sync_to_async(send_legacy_sms, thread_sensitive=True)
result = await async_send_sms("010-1234-5678", "Hello")레거시 동기 코드를 어쩔 수 없이 사용해야 할 때만 최후의 수단으로 활용
스레드 오버헤드 발생으로 성능에 민감한 코드에서는 남용 금지
async def 사용 기준
사용 권장: 여러 I/O 작업을 동시 실행 가능하거나, 단일 I/O 작업이 매우 느린 경우 (500ms 이상)
사용 불필요: CPU 바운드 작업이 주를 이루는 경우
배포 환경 고려사항
ASGI 서버 필요: Uvicorn 사용 (WSGI 서버로는 동작 불가)
커넥션 풀링: 더 많은 동시 요청 처리로 인한 DB 커넥션 부족 방지를 위해 PgBouncer 등 활용
실무 배포: Gunicorn + Uvicorn Worker 조합 사용
gunicorn myproject.asgi:application -k uvicorn.workers.UvicornWorker -w 4Last updated
