[Open WebUI] asyncio.to_thread로 heartbeat DB 쓰기 이벤트 루프 블로킹 해소
PR 링크: open-webui/open-webui#22980 상태: Merged | 변경: +1 / -1
들어가며
async 웹 서버에서 가장 흔한 성능 실수 중 하나는 async 핸들러 안에서 동기 I/O를 호출하는 것입니다. Open WebUI의 WebSocket heartbeat 핸들러에서도 이 문제가 있었습니다. 접속 중인 사용자마다 30초마다 실행되는 heartbeat에서 동기 DB 호출이 이벤트 루프를 블로킹하고 있었습니다.
핵심 코드 분석
Before (socket/main.py):
@sio.on('heartbeat')
async def heartbeat(sid, data):
user = SESSION_POOL.get(sid)
if user:
SESSION_POOL[sid] = {**user, 'last_seen_at': int(time.time())}
Users.update_last_active_by_id(user['id'])
After:
@sio.on('heartbeat')
async def heartbeat(sid, data):
user = SESSION_POOL.get(sid)
if user:
SESSION_POOL[sid] = {**user, 'last_seen_at': int(time.time())}
await asyncio.to_thread(Users.update_last_active_by_id, user['id'])
왜 이게 좋은가
Users.update_last_active_by_id는 SQLAlchemy를 통한 동기 DB 쓰기 작업입니다async핸들러 내에서 동기적으로 호출하면 해당 DB 쿼리가 완료될 때까지 이벤트 루프 전체가 멈춥니다- 동시 접속자가 100명이면 매 30초마다 100번의 이벤트 루프 블로킹이 발생합니다
asyncio.to_thread는 별도 스레드풀에서 동기 함수를 실행하므로 이벤트 루프를 블로킹하지 않습니다- 반환값을 사용하지 않는 호출이므로 기능적 변경은 전혀 없습니다
- 같은 코드베이스의
get_event_emitter에서Chats.*호출에 이미 사용하고 있는 패턴과 일관성을 맞춘 변경입니다
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [triton] AMD MXFP FA 예제에서 TDM Store 도입으로 Output 저장 최적화
- 현재글 : [Open WebUI] asyncio.to_thread로 heartbeat DB 쓰기 이벤트 루프 블로킹 해소
- 다음글 [CPython] JIT float 연산 최적화 — 유일 참조 피연산자 재사용
댓글