C.W.K.
Stream
Lesson 06 of 07 · published

Error Recovery & Graceful Deployment

~12 min · production, deploy, draining

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

Connection draining

Deploy 동안 새 서버가 connection 0 으로 boot; 기존 서버에 수천 있어. 기존 outright kill 하면 다 code 1006 으로 dump. Graceful 패턴: 클라한테 reconnect 하라고 (새 서버 도달), 자기 disconnect 잠시 기다림, 나머지 code 1001 ('going away') 로 close. Track 4 lesson 5 의 cwkPippa lifespan 예시가 정확히 이 패턴.

클라 message buffering

짧은 disconnect window 동안 클라가 send 시도 가능. Robust 클라가 로컬에 buffer 후 reconnect 시 flush. Track 5 의 ACK 패턴인데 로컬: OPEN 아닐 때 queue, 다음 OPEN 에 send, deploy 때문에 user-typed message 절대 잃지 않음.

Rolling deploy

Sticky session 가진 load balancer 뒤에 한 번에 한 서버씩 내림. 그 서버의 connection 이 나머지 서버로 reconnect. 각 서버 반복. 전역 downtime 0; user 별 transient reconnect.

Code

서버: SIGTERM 에 graceful drain·python
from contextlib import asynccontextmanager
import asyncio, signal

shutdown_event = asyncio.Event()

@asynccontextmanager
async def lifespan(app):
    # Trap SIGTERM so we can drain instead of crashing.
    loop = asyncio.get_running_loop()
    for sig in (signal.SIGTERM, signal.SIGINT):
        loop.add_signal_handler(sig, shutdown_event.set)
    yield
    # Drain phase
    log.info('draining %d connections', manager.total_connections)
    for room in list(manager.rooms):
        await manager.broadcast(room, {
            'type': 'system.reconnect',
            'data': {'reason': 'deploy'},
        })
    await asyncio.sleep(2.0)  # let clients reconnect on their own
    close_tasks = [
        ws.close(code=1001, reason='deploy')
        for ws in list(manager.ws_meta)
    ]
    await asyncio.gather(*close_tasks, return_exceptions=True)
    log.info('drain complete')

app = FastAPI(lifespan=lifespan)
클라: buffered send·javascript
class BufferedSocket {
  constructor(url) {
    this.url = url;
    this.buffer = [];
    this._connect();
  }
  _connect() {
    this.ws = new WebSocket(this.url);
    this.ws.addEventListener('open', () => {
      while (this.buffer.length) this.ws.send(this.buffer.shift());
    });
    this.ws.addEventListener('close', () => {
      // schedule reconnect (Track 2 backoff pattern)
      setTimeout(() => this._connect(), 1_000);
    });
  }
  send(msg) {
    const wire = JSON.stringify(msg);
    if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(wire);
    else this.buffer.push(wire);
  }
}

External links

Exercise

Lifespan drain + BufferedSocket 구현. 테스트 클라 셋 connect 한 채로 kill -TERM <pid> 로 deploy trigger. 확인: (a) 클라가 system.reconnect 받음, (b) buffered message reconnect 후 flush, (c) 새 서버가 ~2s 안에 reconnect 받음, (d) 전체 user 영향 < 1s.

Progress

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

댓글 0

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

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