-
WSGI와 ASGI 환경에서의
threading.local
과contextvars
의 사용 및 차이점 -
WSGI 환경에서는 각 요청마다 별개의 스레드가 생성되기 때문에
threading.local
을 사용해 스레드별로 독립된 데이터 저장소를 관리할 수 있음. 하지만, ASGI 환경에서는 하나의 스레드 안에서 여러 태스크가 동시에 실행되기 때문에threading.local
로는 데이터의 격리가 어려워짐. -
이 문제를 해결하기 위해
contextvars
가 등장했음.contextvars
는 동시성 코드에서 현재 컨텍스트에 대한 데이터를 독립적으로 관리할 수 있는 방법을 제공함. 각각의 비동기 태스크가 자신만의 컨텍스트를 가지고, 이 컨텍스트는 다른 태스크와 공유되지 않아 데이터가 서로 침해받지 않음. -
아래 예제 코드는
contextvars
를 사용하여 여러 "평행세계"(비동기 태스크)에서 각각의 데이터를 독립적으로 관리하는 방법을 보여주고 있음. 이는 Python 3.7부터 사용 가능한 기술로, 동시성 코드에서 컨텍스트 관리를 더욱 쉽고 명확하게 할 수 있게 해줌.
# https://velog.io/@kjyggg/python-uvicorn-%EC%8B%A4%ED%96%89-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4
import contextvars
import asyncio
## who_am_i 표식이 만들어짐
who_am_i = contextvars.ContextVar("who_am_i")
def do_something(some):
who_am_i.set(some)
async def universeA():
## 표식에 universeA 남기기
who_am_i.set("universeA")
## 사진찍기-copy_context
context = contextvars.copy_context()
print(f"in universeA:{list(context.items())}")
## 사진찍은 시점으로 들어가기, universeA 를 destroy
context.run(do_something, "destory universeA")
print(f"after destory context-inner who_am_i:{context.get(who_am_i)}")
print(f"after destory context-outer who_am_i:{who_am_i.get()}")
async def universeB():
who_am_i.set("universeB")
context = contextvars.copy_context()
print(f"in universeB:{list(context.items())}")
async def universeC():
who_am_i.set("universeC")
context = contextvars.copy_context()
print(f"in universeC:{list(context.items())}")
async def universeD():
who_am_i.set("universeD")
context = contextvars.copy_context()
print(f"in universeD:{list(context.items())}")
async def universeE():
who_am_i.set("universeE")
context = contextvars.copy_context()
print(f"in universeE:{list(context.items())}")
async def cross_universe():
a = asyncio.create_task(universeA())
b = asyncio.create_task(universeB())
c = asyncio.create_task(universeC())
d = asyncio.create_task(universeD())
e = asyncio.create_task(universeE())
await asyncio.gather(a, b, c, d, e)
if __name__ == "__main__":
asyncio.run(cross_universe())
- FastAPI와 같은 비동기 웹 프레임워크는 기본적으로 ASGI 서버가 필요함.
- ASGI는 WSGI(Web Server Gateway Interface)의 비동기 버전으로, 한 쓰레드에서 여러 코루틴이 동시에 처리될 수 있음.
- WSGI 환경에서는 각 요청이 별도의 스레드에서 처리되어,
threading.local
을 사용해 각 스레드별로 데이터를 독립적으로 관리할 수 있었음. 그러나 ASGI에서는 하나의 스레드 내에서 여러 코루틴이 동시에 실행되므로,threading.local
을 사용하면 요청 간 컨텍스트가 섞일 수 있음. - 이를 해결하기 위해
contextvars
가 도입됨.contextvars
는 각 비동기 태스크별로 독립적인 컨텍스트를 관리할 수 있게 해줌. - FastAPI는 ASGI 기반의 프레임워크임. 이를 위해 ASGI 서버가 필요하며, 대표적으로 uvicorn, hypercorn, daphne 등이 사용될 수 있음. 이러한 서버들은 요청을 비동기적으로 처리해 여러 코루틴이 동시에 실행되게 함.
- FastAPI와 같은 비동기 웹 애플리케이션에서는
contextvars
를 사용해 각 요청이나 태스크별로 고유한 컨텍스트를 유지하고 관리할 수 있음. 이를 통해 요청이나 작업별로 필요한 데이터를 안전하게 분리하여 사용할 수 있음. - FastAPI의 미들웨어에서 클라이언트의 각 요청을 처리할 때
contextvars
를 사용하여 요청별 고유한 정보(예: 사용자 ID, 요청 ID 등)를 저장하고 관리할 수 있음. 이는 요청의 생명주기 동안 일관된 데이터 관리가 가능함.