피드로 돌아가기
Dev.toBackend
원문 읽기
Stateful Monolith 탈피를 통한 30만 동시 접속 처리 및 p99 15ms 달성
The Moment We Realized Our Treasure Hunt Engine Was Lying to Us
AI 요약
Context
Elixir 기반 Stateful Monolith 구조에서 21만 명 이상의 유저 유입 시 PostgreSQL/Redis 복제 지연 및 GenServer 메모리 압박 발생. 특히 단일 프로세스가 수백만 명의 상태를 Map 형태로 관리함에 따른 GC 부하와 OOM 현상이 시스템 병목의 핵심 원인으로 분석됨.
Technical Solution
- Hunt State Service(HSS) 도입을 통한 연산과 상태의 완전한 분리 및 Stateless Go 기반 gRPC 아키텍처 설계
- Immutable Event Log 관점으로의 전환을 통해 모든 액션을 Kafka Protobuf 이벤트로 발행하는 Event-Driven 구조 채택
- Citus PostgreSQL의 ORM Join을 제거하고 hunt_id 기반의 단일 Lookup 및 modulo 256 샤딩을 통한 데이터 분산 최적화
- NATS JetStream 기반의 Event Fan-out Service(EFS) 구축으로 재연결 시 상태 재계산 없는 메모리 맵 파일 스트리밍 구현
- Redis를 권위 있는 상태 저장소가 아닌 Read-through Cache로 한정하여 쓰기 부하 분산 및 데이터 일관성 확보
Impact
- Event 처리 성능: HSS 복제본당 초당 약 46k 이벤트 처리 및 p99 20ms 미만 달성
- Fan-out 지연 시간: NATS JetStream 도입 후 30만 명 동시 재연결 시에도 p99 15ms 미만 유지
- 인프라 효율성: Shard당 데이터 크기를 200GB 미만으로 유지하며 메모리 압박 문제 해결
Key Takeaway
수직 확장이나 단순 샤딩만으로는 Stateful Monolith의 GC 부하와 데이터 일관성 문제를 해결할 수 없으며, 상태를 불변의 이벤트 로그로 취급하는 CQRS 및 Event-Sourcing 패턴으로의 전환이 고가용성 분산 시스템 설계의 핵심임.
실천 포인트
- 대규모 상태 관리 시 GenServer/Actor 모델의 단일 프로세스 메모리 맵 사용 여부 검토 - 분산 DB 도입 시 ORM Join으로 인한 Fan-out 쿼리 발생 가능성 및 Query Push-down 지원 여부 확인 - 상태 복구 로직 설계 시 전체 상태 재계산 방식 대신 Event Log 기반의 스트리밍 복구 방식 고려 - WebSocket 대규모 Fan-out 필요 시 Redis Pub/Sub의 단일 스레드 제약을 대체할 NATS 등 전문 메시징 브로커 검토