프로젝트 요약
•
목표: 실시간 채팅 데이터 수집기 구축
•
트래픽 규모: 일일 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)
