본문 바로가기
C.W.K.
Stream
Lesson 05 of 07 · published

우아한 서버 종료

~11 min · management, lifespan, shutdown

Level 0Poller
0 XP0/60 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

Uvicorn 을 다시 시작하면 생기는 일

아무 조치도 하지 않으면 다시 시작할 때 모든 WebSocket 연결이 비정상 종료 코드 1006 으로 끝나. 클라이언트는 네트워크 오류를 보고 재연결 로직을 실행하니 작동은 해. 하지만 더 잘할 수 있어. 먼저 system.reconnect 메시지로 알리고 1001 ‘going away’ 코드로 깔끔하게 닫는 거야.

FastAPI lifespan

lifespan 컨텍스트 관리자를 사용해. yield 뒤에서 실행되는 모든 코드가 종료 단계야. 활성 WebSocket 을 모두 닫은 뒤 반환하면 되고, Uvicorn 은 lifespan 이 반환될 때까지 프로세스를 끝내지 않아.

gather 로 동시에 닫기

연결 10,000개를 하나씩 닫으면 몇 분이 걸릴 수 있어. asyncio.gather(*close_tasks, return_exceptions=True) 로 닫기 작업을 한꺼번에 시작하고 함께 기다려. return_exceptions=True 는 문제가 있는 클라이언트 하나 때문에 나머지 작업까지 중단되는 것을 막아.

Code

lifespan 으로 관리하는 종료·python
from contextlib import asynccontextmanager
from fastapi import FastAPI
import asyncio

@asynccontextmanager
async def lifespan(app: FastAPI):
    yield  # serve

    # Shutdown phase
    log.info('draining %d WebSocket connections', manager.total_connections)
    # 1. Tell clients we're going.
    for room in list(manager.rooms):
        await manager.broadcast(room, {
            'type': 'system.reconnect',
            'data': {'reason': 'server-restart'},
        })

    # 2. Brief grace period so clients receive the message.
    await asyncio.sleep(1.0)

    # 3. Close all connections concurrently.
    close_tasks = [
        ws.close(code=1001, reason='server-restart')
        for ws in list(manager.ws_meta)
    ]
    await asyncio.gather(*close_tasks, return_exceptions=True)
    log.info('all WebSocket connections drained')

app = FastAPI(lifespan=lifespan)

External links

Exercise

위 lifespan 을 서버에 추가하고 SIGTERM 을 보내. 클라이언트는 system.reconnect 메시지를 받은 뒤 정상적인 1001 종료를 봐야 해. lifespan 을 빼면 갑작스러운 1006 종료가 나타나는지도 비교해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.