LabHub
배우기 러닝패스 코스

느린 구독자가 생중계를 멈췄다 · 무엇을 버리고 누구를 기다릴까 · 이론

무엇을 버리고 누구를 기다릴까

LabHub 에서 이어서 보기

한 줄 요약

최신 위치와 결제 이력은 같은 이벤트가 아닙니다. 무엇을 잃어도 되는지에 따라 느린 구독자 정책이 달라집니다.

왜 이게 필요했나

구조대가 이미 다음 골목으로 이동했는데 지도에 5분 전 위치가 차례로 재생됩니다. 서버는 한 건도 잃지 않았다고 보고하지만 사용자에게는 쓸모없는 생중계입니다. 반대로 결제 이력에서 오래된 항목을 지우고 최신 항목만 보여 주면 돈의 이동 자체를 누락할 수 있습니다. 손실을 없애는 것과 서비스 목적을 지키는 것은 언제나 같은 선택이 아닙니다.

이 모듈은 세 가지 정책을 비교합니다. 공간이 생길 때까지 기다리기, 가장 오래 기다린 상태를 버리기, 느린 구독자를 발행 대상에서 분리하기입니다. 모두 장단점이 있습니다. 큐 크기 하나를 설정하면 정책도 자동으로 결정된다고 생각하지 마세요. QueueFull 뒤에 무엇을 하는지가 제품의 동작을 결정합니다.

어떻게 동작하나

대기 정책은 await queue.put으로 구현할 수 있습니다. 데이터를 몰래 버리지는 않지만, 발행자가 구독자 목록을 순회하며 각 put을 기다리면 느린 구독자 하나에서 전체 순회가 멈춥니다. 구독자마다 큐를 두었다고 자동으로 격리되는 것이 아닙니다. 생산자가 그 큐들을 어떤 순서와 방식으로 기다리는지도 보아야 합니다.

최신 상태 정책은 큐가 가득 찼을 때 가장 오래 기다린 항목을 하나 꺼내고 새 항목을 넣습니다. 용량 2에 10, 11이 기다리는데 12가 오면 11, 12가 남습니다. 방금 도착한 12를 버리면 이름은 latest여도 실제로는 오래된 화면을 보존하는 정책입니다. 이미 작업자가 가져가 처리 중인 항목은 큐에서 뺄 수 없으므로 이 정책은 진행 중 작업을 되감지 않습니다.

버린 항목도 get으로 큐에서 가져왔으므로 task_done을 한 번 대응시킵니다. 그렇지 않으면 queue.join이 영원히 기다릴 수 있습니다. 이때 join이 끝났다고 전부 성공한 것은 아닙니다. 일부는 의도적으로 폐기했고 일부는 처리에 실패했을 수도 있습니다. 성공·폐기·실패 횟수는 업무 지표로 별도 기록해야 합니다. 수업에서는 버린 항목을 반환해 정책을 눈으로 비교합니다.

분리 정책은 가득 찬 큐를 몰래 수정하지 않고 SlowConsumer 예외를 냅니다. 발행 함수는 해당 구독자를 딕셔너리에서 제거하고 소유자에게 종료를 요청합니다. 남아 있는 구독자에게는 계속 전달합니다. 첫 실패에서 return해 버리면 목록에서 그 뒤에 있는 정상 구독자가 이벤트를 놓칩니다. 수정 중인 딕셔너리를 그대로 순회하면 순회 오류가 생길 수 있어 이름·큐 쌍의 스냅샷을 사용합니다.

| 데이터 | 고려할 정책 | 추가로 필요한 것 |
| --- | --- | --- |
| 현재 위치·진행률 | 오래된 대기 상태 생략 | 최신 스냅샷, 누락 표시 |
| 채팅·알림 이력 | 느린 연결 분리 후 재접속 | 보관된 로그, 마지막 확인 위치 |
| 결제·재고 변경 | 내구성 있는 저장과 재시도 | 멱등 처리, 원자적 상태 변경 |

연결을 끊으면 서버 자원을 회수할 수 있지만 전달 문제까지 해결되지는 않습니다. 구독자가 이벤트를 받았으나 ACK를 보내기 전에 끊겼다면 서버는 처리 여부를 모릅니다. 재시도하면 중복 가능성이 있고, 재시도하지 않으면 누락 가능성이 있습니다. 분리는 장애 범위를 줄이는 조치이며 전달 보장과는 별도 설계입니다.

현장에서 만나는 모습

대시보드는 초당 수십 번 바뀌는 CPU 사용률의 모든 중간값을 그릴 필요가 없을 수 있습니다. 하지만 감사 로그를 같은 방식으로 압축하면 사고 경로를 잃습니다. 채널마다 정책을 명시하고, 느린 연결을 분리한 횟수와 구독자가 다시 동기화하는 시간을 함께 관측해야 합니다. 끊는 횟수만 늘어도 정상 구독자의 지연 지표는 좋아 보일 수 있습니다.

이 실습의 마지막 단계는 연결 분리 정책을 실제 TCP에서 검증합니다. fast는 매 이벤트를 ACK하고 slow는 첫 이벤트의 ACK를 보류합니다. 0부터 8까지 발행했을 때 fast가 전부 받는지, slow만 한 번 분리되는지를 확인합니다. 고정된 sleep에 기대지 않고 ACK와 이벤트 장벽을 사용해 실행 순서를 확인합니다. 이것은 처리량 벤치마크가 아니라 부분 장애의 기능 검증입니다.

다음 확인에서 할 것

같은 입력으로 정책의 차이를 적어 보세요. slow가 0을 처리 중이고 용량 2 큐에 1과 2가 기다릴 때 3이 도착합니다. 대기 정책은 발행자를 멈춥니다. 최신 상태 정책은 1을 버리고 2와 3을 남깁니다. 분리 정책은 이번 삽입을 거부하고 연결 소유자에게 알립니다. 분리된 큐 안의 1과 2를 누가 정리할지도 반드시 정해야 합니다. 어느 경우에도 이미 처리 중인 0의 성공 여부가 저절로 확정되지는 않습니다.

이 차이를 로그 한 줄의 성공·실패로만 압축하면 원인을 놓칩니다. 대기열 포화, 업무 ACK 시간 초과, 원격 EOF, 관리자의 강제 분리는 서로 다른 사건입니다. 지표 이름과 실패 메시지를 구분하면 재접속을 늘려야 하는지, 소비 속도를 개선해야 하는지, 데이터 정책을 바꿔야 하는지 판단하기 쉬워집니다. 단순히 오류를 모두 잡아 계속 보내는 방식은 생중계가 살아 보이면서 데이터만 사라지는 결과를 만들 수 있습니다.

각 정책이 누구를 기다리게 하고 무엇을 잃는지 퀴즈로 확인합니다. 뒤의 실습에서 offer_latest, offer_disconnect, broadcast를 구현하며 같은 입력을 다르게 처리하는 이유를 설명해 보세요.