← all posts
DEV 2026.05.02 · 15 min read Intermediate

대규모 데이터 시스템, 무엇을 포기하고 무엇을 얻는가

Lambda/Kappa 아키텍처 선택부터 시계열 DB 다운샘플링, Redis Cluster 슬롯 분산, 샤딩 전략, 글로벌 복제 일관성까지 — 데이터 시스템 설계의 근본 트레이드오프를 추적한다.


대규모 데이터 시스템을 설계할 때 “정답”이란 없다. 있는 것은 트레이드오프의 선택뿐이다. Lambda를 쓸 것인가 Kappa를 쓸 것인가, 시계열 데이터를 얼마나 오래 원본 해상도로 보존할 것인가, 샤드 키를 무엇으로 잡을 것인가, 글로벌 복제를 동기로 할 것인가 비동기로 할 것인가 — 이 모든 결정은 결국 하나의 질문으로 수렴한다. 무엇을 포기하고 무엇을 얻을 것인가?

파이프라인: 코드를 한 번 쓸 것인가, 두 번 쓸 것인가

데이터 파이프라인 아키텍처 논쟁의 핵심은 “실시간”과 “정확성” 사이의 긴장이다.

Lambda 아키텍처는 이 긴장을 병렬 레이어로 해소한다. Batch Layer는 매일 자정 전날 데이터를 완전히 재계산해 정확한 뷰를 만들고, Speed Layer는 Flink/Spark Streaming으로 최근 수분~수시간의 대략적 실시간 결과를 만든다. 사용자는 두 결과의 합집합을 본다. 문제는 같은 집계 로직을 배치 코드와 스트리밍 코드로 두 번 작성해야 한다는 것이다. 유지보수 비용이 두 배가 되고, 두 결과가 미묘하게 불일치할 위험이 항상 존재한다.

Kappa 아키텍처는 이 중복을 잘라낸다. Kafka의 보존 기간을 길게 설정하고(7~30일), 모든 처리를 단일 스트리밍 엔진으로 통일한다. 집계 로직을 수정해야 할 때는 Kafka 처음부터 다시 읽어 재처리한다. Netflix와 LinkedIn이 이 방식을 채택한 이유다. 단, Kafka 스토리지 비용이 증가하고 수백 TB 규모의 재처리는 느릴 수 있다.

트레이드오프

Lambda는 기존 배치 인프라가 이미 존재할 때 유리하다. 신규 구축이고 스트리밍 중심이라면 Kappa가 코드 단순성과 일관성 면에서 우위다. 배치만으로 분석 지연을 허용할 수 있다면 — 그게 가장 단순한 선택이다.

시계열: 시간이 지날수록 해상도를 낮춘다

서버 1만 대에서 메트릭 100개를 10초마다 수집하면 초당 100,000 쓰기가 발생한다. 이를 1년 원본 그대로 보존하면 156TB에 달한다. 시계열 데이터 시스템의 핵심 설계 결정은 여기서 나온다 — 시간이 지날수록 해상도를 낮추는 Rollup 전략.

0 ~ 7일:    원본 (10초 해상도)
7일 ~ 1달:  1분 평균으로 롤업   → 1/6 데이터
1달 ~ 1년:  1시간 평균으로 롤업 → 1/360 데이터

이 전략만으로 156TB를 약 5TB로 줄일 수 있다(30배 절감). InfluxDB의 Continuous Query가 이 롤업을 자동화한다.

또 다른 함정은 고카디널리티다. host × service × region × environment 조합이 3,000만 시계열을 만들면 InfluxDB는 각 시계열마다 인덱스 항목을 메모리에 유지하다 OOM으로 죽는다. 선택지는 세 가지다 — 태그 카디널리티를 제한하거나, 고카디널리티에 강한 ClickHouse로 전환하거나, Prometheus + VictoriaMetrics 조합을 쓰거나.

분산 캐시: 슬롯, 잠금, 스탬피드

Redis Cluster는 16,384개 해시 슬롯으로 데이터를 분산한다. 키의 슬롯은 CRC16(key) % 16384로 결정된다. 여기서 Hash Tag가 중요해진다.

"user:{1001}:profile"  → CRC16("1001") → 슬롯 X
"user:{1001}:session"  → CRC16("1001") → 같은 슬롯 X
"user:{1001}:cart"     → CRC16("1001") → 같은 슬롯 X

중괄호 안의 값만으로 슬롯을 결정하므로, 사용자 관련 키 전체가 같은 노드에 배치된다. Multi-get을 단일 노드에서 처리할 수 있게 된다. 단, Hash Tag를 남용해 {user}:1001, {user}:1002처럼 쓰면 모든 키가 "user" 슬롯 하나에 몰려 핫 샤드가 된다.

분산 환경의 잠금은 단순하지 않다. Primary 장애 후 Replica가 승격되는 순간, 아직 복제되지 않은 잠금이 사라지면서 두 클라이언트가 동시에 잠금을 획득할 수 있다. Redlock은 5개의 독립된 Redis 인스턴스에서 과반수(3개) 획득을 요구함으로써 이 문제를 완화한다.

TTL 만료 순간의 Cache Stampede도 함정이다. 동시에 수백 개의 요청이 DB로 쏟아진다. PER(Probabilistic Early Recomputation) 알고리즘은 TTL이 충분히 남아 있어도 확률적으로 미리 갱신함으로써 한 클라이언트만 DB를 조회하게 만든다.

샤딩: 무엇을 기준으로 나눌 것인가

샤딩 전략 선택은 쿼리 패턴에서 출발해야 한다.

방식균등 분산범위 쿼리리샤딩핫샤드 방지
Hash어려움
Range쉬움
Directory제어 가능보통유연수동 제어
Consistent Hash최소 이동보통

Hash Sharding은 균등 분산에 강하지만 샤드 수가 바뀌면 대부분의 키가 재배치된다. Range Sharding은 시간 기반 조회에 적합하지만 최신 데이터 샤드에 쓰기가 집중되는 핫샤드 문제가 따라온다. Consistent Hashing은 노드 추가 시 K/N개 키만 이동하므로 동적 확장에 유리하다.

샤드 수를 늘려야 할 때의 온라인 리샤딩 절차도 중요하다 — 더블 쓰기 기간(이전 샤드와 새 샤드 모두 쓰기) → 백그라운드 데이터 복사 → 점진적 트래픽 전환(1% → 10% → 50% → 100%) → 이전 샤드 정리. 한 번에 전환하면 위험하다.

글로벌 분산: 지연과 일관성의 교환

서울과 미국 동부 사이 RTT는 약 200ms다. 글로벌 분산 시스템에서 강한 일관성을 유지하려면 모든 쓰기가 이 왕복 지연을 감수해야 한다. Google Spanner가 TrueTime API(GPS + 원자 시계)로 이를 해결했지만, 전용 하드웨어가 필요하다.

대부분의 실무 선택은 비동기 복제 + 최종 일관성이다. 서울 Primary에 쓰고 즉시 응답한 뒤, 비동기로 도쿄와 미국에 복제한다. 서울 장애 시 복제되지 않은 데이터가 유실될 수 있다(RPO > 0). 같은 대륙 내(서울↔도쿄, 30ms)에서는 동기 복제를 허용하고, 다른 대륙 간에는 비동기를 쓰는 준동기(semi-synchronous) 방식이 현실적 절충점이다.

동시 수정 충돌은 CRDT로 해결한다. G-Counter 방식으로 좋아요 수를 집계하면 서울과 도쿄가 동시에 다른 값을 기록해도 병합 결과는 항상 동일하다. 순서 없이 합쳐도 같은 값이 나오는 수학적 보장이다.

정리

  • 파이프라인 아키텍처는 “코드 중복과 정확성(Lambda)” vs “단일 코드와 일관성(Kappa)” 사이의 선택이다.
  • 시계열 데이터는 시간이 지날수록 해상도를 낮추는 Rollup으로 수십 배 공간을 절감한다.
  • Redis Cluster의 Hash Tag는 Multi-get을 최적화하지만 과용하면 핫 샤드를 만든다. Cache Stampede는 PER 알고리즘으로 완화한다.
  • 샤딩 전략은 쿼리 패턴에서 출발한다. 리샤딩은 더블 쓰기와 점진적 전환으로 온라인 처리한다.
  • 글로벌 복제는 지연과 일관성의 교환이다. 비동기 복제 + CRDT가 현실적인 균형점이다.

다음 글에서는 이 데이터 시스템 위에서 동작하는 신뢰성 설계 — 장애를 전제하고 설계하는 Fault Tolerance 패턴을 추적한다.