Search

EKS 기반 실시간 채널 수집기 설계

프로젝트 요약

목표: 실시간 채팅 데이터 수집기 구축
트래픽 규모: 일일 10만~100만건 유동
핵심 요구사항:
채널별 WebSocket 연결 유지
실시간 수집 (누락 불가)
채널 목록은 유동적 (관리 대상 변경 대응 필요)
장애 발생 시 자동 복구
구조는 MSA 형태 지향 (기능별 서비스 분리 가능해야 함)

개요

EKS 기반의 수집기 인스턴스가 여러 개 생성되는 경우 채널 싱크에 따라 유실되거나 중복되는 메시지가 발생하고 장애 대응에 취약할 수 있어 아래와 같은 전략을 구상
EKS에서 수집기(Pod)를 여러 개 운영할 때, 중복 수집 방지장애 복구, 유실 최소화를 달성하기 위한 구조 설계
채널은 동적으로 할당, 수집기는 최대 N개의 채널만 담당

주요 구조

[Redis] ├── lock:channel:<id> // 채널 수집 락 (TTL 기반) ├── heartbeat:channel:<id> // 수집기 생존 상태 └── channel:list // 수집 대상 전체 채널 목록 [Collector Pod] ├── Redis에서 채널 목록 조회 ├── 락 획득(N개) 후 WebSocket 연결 ├── 주기적 TTL 갱신 (lock + heartbeat) ├── 종료 시 자동 TTL 만료 → 다른 Pod가 takeover └── 내부에서 Map<channelId, context>로 수집 채널 관리
Plain Text
복사

수집기 동작 방식

1.
Redis에서 channel:list 조회
2.
채널마다 SET lock:channel:<id> podId NX EX 30 시도
3.
성공한 채널만 WebSocket 연결
4.
연결 유지 중 EXPIRE로 TTL 연장
5.
장애 발생 시 TTL 만료 → 다른 Pod가 동일 방식으로 락 재시도

Redis 분산 락 사용 이유

문제
Redis 락으로 해결됨
여러 Pod가 같은 채널 수집
NX 락으로 단일 소비자 보장
수집기 장애 → 자동 failover
TTL 만료 후 재선점 가능
수집 대상 균등 분배
Pod 수에 비례해 락 N개만 획득

채널 동적 할당 로직 (예: 채널 10개만 처리)

ts 복사편집 const acquired = 0 for (const channelId of candidateChannels) { const success = tryLock(channelId, podId) if (success) { startCollect(channelId) acquired++ if (acquired >= 10) break } }
TypeScript
복사

장애 대응 전략

문제
대응 방식
수집기 Pod 죽음
TTL 만료 후 다른 Pod가 락 재획득
어떤 채널이 죽었는지 모름
heartbeat:channel:<id> 키로 모니터링
메시지 유실 가능성 존재
WebSocket 서버에 lastSeq로 resume 요청 필요
특정 Pod 과부하
수집 채널 수 제한 (예: max 10개)

구현 시 체크리스트

채널 락: SET key NX EX
락 TTL 주기: 30초
heartbeat: 10초 주기 갱신
수집 채널 수 제한 (환경변수로 설정 가능)
수집기 종료 시 graceful shutdown
로그에 연결 상태, 종료 이유, 수집 통계 남기기

확장 포인트

Watcher 서비스 도입 → heartbeat 누락 감지 후 알림
채널 증가 시 자동 Pod 수 조정 (HPA 연계)
메시지 시퀀스 기반 resume 수집 구조 도입
수집기 상태 시각화 (Grafana/Prometheus)