본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 27. 03:14

단 하나의 5xx 에러 없이 블랙 프라이데이를 견뎌낸 이벤트 스토어

요약

블랙 프라이데이 기간 중 급증한 트래픽과 Kafka Streams의 RocksDB 예외 및 GC 문제를 해결하기 위한 기술적 여정을 다룹니다. Redis와 Kafka Streams의 한계를 극복하기 위해 계층형 이벤트 소싱 아키텍처를 도입하여 안정성을 확보했습니다.

핵심 포인트

  • Kafka Streams의 RocksDB 압축 및 JVM GC로 인한 지연 문제 분석
  • Redis 클러스터의 메모리 점유 및 DragonflyDB의 스냅샷 지연 한계 확인
  • ZGC 도입을 통한 GC 일시 중지 시간 단축 효과
  • 계층형 이벤트 소싱 및 커스텀 세그먼트 라우터 도입을 통한 해결

우리가 실제로 해결하고 있었던 문제

2025년 11월 26일 02:47, 프로모션 서비스(promotions service)의 이벤트 피드(event feed)가 3분도 채 되지 않아 초당 22,000개(22k)에서 127,000개(127k)로 급증했습니다. 3개의 노드로 구성된 Kafka Streams 클러스터에서 실행 중이던 이벤트 프로세서(event processor)는 2GB 크기의 변경 로그(changelog) 세그먼트를 압축(compact)하려고 시도할 때마다 RocksDB 이터레이터 예외(RocksDB iterator exceptions)를 발생시키기 시작했습니다. 우리는 블록 캐시(block cache)를 1GB로, 쓰기 버퍼 매니저(write buffer manager)를 512MB로 설정하여 RocksDB를 튜닝했지만, 압축 스레드(compaction threads)는 여전히 세그먼트당 400ms를 소모했고 파티션 7(partition 7)의 지연(lag)은 38분까지 늘어났습니다. 당직 엔지니어(on-call engineer)가 포드(pod)를 재시작하자 지연은 회복되었지만, 한 시간도 채 되지 않아 CFO가 전화를 걸어와 왜 쿠폰 사용(coupon-redemption) 그래프가 하키 스틱 모양처럼 나타나는지 물었습니다.

우리가 처음 시도했던 것 (그리고 실패한 이유)

우리는 핫 패스(hot path) 방식으로 시작했습니다. 마지막 30초 동안의 이벤트를 Redis 클러스터에 유지하고, 나머지 모든 데이터는 Firehose를 통해 S3로 스트리밍하는 방식이었습니다. Redis의 점유 공간(footprint)은 50k TPS(초당 트랜잭션 수)에서 95GB에 도달했고, 클러스터는 30k RPS(초당 요청 수)에서 키를 축출(evicting)하기 시작했습니다. 우리는 64개의 샤드(shards)와 256GB RAM을 갖춘 DragonflyDB로 전환했지만, 포크 기반의 지속성(fork-based persistence)은 여전히 스냅샷(snapshot) 쓰기 중에 80ms의 p99 꼬리 지연 시간(tail latency) 스파이크를 유발했습니다. 그 다음에는 인메모리 상태 저장소(in-memory state stores)와 정확히 한 번 실행(exactly-once semantics)을 사용하는 Kafka Streams를 시도했습니다. 문제는 상태 저장소의 크기가 아니라, JVM이 15초간의 GC(Garbage Collection) 사이클을 위해 일시 중지되는 동안 매시간 10분을 허비한다는 점이었습니다. ZGC로 전환하여 GC 일시 중지 시간을 1.2ms로 줄였지만, 진짜 병목 현상은 변경 로그 복제 계수(changelog replication factor)에 숨어 있었습니다. 결함 허용(fault tolerance)을 위해 replication.factor=3으로 설정했지만, 컨트롤러 로그(controller log)가 300GB였기 때문에 모든 파티션 리더 재균형(partition leader rebalance)이 발생할 때마다 12초간의 재균형 폭풍(rebalance storm)이 발생했습니다.

아키텍처 결정

우리는 하이브리드 캐시 모델을 버리고, 커스텀 세그먼트 라우터(custom segment router)를 갖춘 계층형 이벤트 소싱(tiered event sourcing)에 올인했습니다. 모든 이벤트는 event_id, source_ts, segment_id라는 헤더(header)를 포함합니다. 세그먼트 라우터는 엣지(edge)에 상주하며 세 개의 계층으로 팬 아웃(fan out)됩니다:

  1. 최근 5초간의 이벤트를 위한 jemalloc 내의 Unbounded ring buffer (캐시 라인 정렬(cache line aligned), 잠금(lock) 없음).
  2. 256 KB 압축 단위(compaction granularity) 및 zstd 압축 레벨 19를 적용한 NVMe 상의 Sharded RocksDB. 세그먼트당 압축 시간(compaction time)을 70ms로 단축하기 위해 write_buffer_size=128 MB 및 max_write_buffer_number=4로 설정했습니다.
  3. 불변의 감사 추적(immutable audit trail)을 위한 S3 Deep Archive. RAM에 128 KB 이상 버퍼링되지 않도록 PutObject 스트리밍을 통해 전송합니다.

핵심 결정 사항은 세그먼트 지역성(segment locality)을 강제하는 것이었습니다. 라우터는 파티션 변경과 관계없이 이벤트를 10초 동안 동일한 NVMe 노드에 고정(pin)합니다. 블랙 프라이데이 부하 상황에서 세그먼트 지역성 적중률(segment-locality hit rate)을 99.8%로 측정했으며, 이에 따라 클러스터가 재구성(reshuffle)되는 동안에도 노드 간 트래픽(cross-node traffic)을 1.3 Gbps 미만으로 유지했습니다.

사후 수치 분석

스테이징(staging) 환경에서 14일 동안 블랙 프라이데이 트래픽을 재현(replay)했습니다. 새로운 프로세서는 이전에 32 k TPS에서 한계에 부딪혔던 동일한 3개 베어메탈(bare-metal) 노드에서 p99 기준 420 ms에 164 k TPS를 처리했습니다. 세그먼트 크기를 2 GB에서 512 MB로 줄이고 레벨드 압축(leveled compaction) 방식으로 전환함에 따라 압축(compaction) CPU 사용량이 45%에서 12%로 감소했습니다. 전체 노드 손실 시 복구 시간(recovery time)은 Kafka 미러링(mirroring) 대신 S3 멀티파트 업로드(multipart upload)를 통해 RocksDB 스냅샷을 스트리밍함으로써 13분에서 89초로 단축되었습니다.

비즈니스 영향: 쿠폰 사용량이 4.7배 급증했으나, 우리의 이벤트 기반(event-driven) 사기 필터는 여전히 이상 패턴의 98.6%를 잡아냈습니다. 유일한 장애는 DNS 엔드포인트를 오래된 로드 밸런서(load balancer)로 지정한 DNS 설정 오류였으며, 이는 인프라 문제였지 이벤트 스토어의 문제는 아니었습니다.

내가 다르게 했을 것들

저는 핫 패스 (hot path)를 위해 Kafka Streams를 선택하지 않았을 것입니다. 블랙 프라이데이(Black Friday) 문제 해결 이후, 저희는 계층형 스토리지 (tiered storage)가 활성화되고 64개의 BookKeeper 레저 (ledger)를 사용하는 Apache Pulsar로 마이그레이션했습니다. 레저 압축 (ledger compaction)은 백그라운드에서 실행되므로 크리티컬 패스 (critical path)를 절대 차단하지 않습니다. 또한 에지 (edge)에서 세그먼트 라우터 (segment router)를 샤딩 (sharding)하여 각 에지 POP (Point of Presence)가 앞으로 보게 될 세그먼트만을 소유하도록 함으로써, 교차 리전 이그레스 (cross-region egress) 비용을 60% 절감했습니다. 비용 차이를 살펴보면, 추가 NVMe 노드 비용으로 월 18,000달러를 더 지출했지만, Redis 클러스터의 과다 프로비저닝 (over-provisioning) 비용을 월 42,000달러 절감했으며, 세일 기간 중 주당 5회였던 온콜 (on-call) 호출을 0회로 줄였습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0