콘텐츠로 이동

데이터베이스 4편 - 분산 시스템, 책임과 결정권의 분리

Published: May 26, 2026

2023년, Discord는 수조 개의 메시지를 처리하기 위해 177개 노드의 Cassandra 클러스터를 운영하고 있었다. 데이터는 수치적으로 고르게 분배되어 있었으나, 수십만 명이 상주하는 대형 채널 하나가 트래픽을 독점하면서 시스템에 부하가 생겼다. 해당 파티션을 담당하는 노드의 시간이 지연되자, 같은 노드를 공유하던 다른 채널들까지 연쇄적으로 마비된 것이다.

2012년, Instagram 역시 초당 25장의 사진과 90개의 좋아요를 처리하며 단일 PostgreSQL 데이터베이스의 임계점에 도달해 있었다. 데이터를 여러 서버로 쪼개기로 결정했을 때, 그들이 마주한 난관은 ‘ID 발급’이었다. 단일 DB 시절의 자동 증가(auto-increment) 규칙은 서버가 분산되는 순간 동시 충돌을 일으킨다. 그들은 각 서버가 독자적으로 데이터의 유일성을 보장할 규칙이 필요하다는 사실을 깨달았다.

Reference: Discord: How Discord Stores Trillions of Messages
Reference: Instagram: Sharding & IDs at Instagram

전자는 데이터를 균등하게 나누고도 불균형한 트래픽 때문에 무너졌고, 후자는 분산 과정에서 발생할 ID 충돌 문제를 해결하며 버텼다.

회사 조직에서도 이러한 일들이 빈번히 일어난다. 규모가 커지면 자연히 중앙 통제가 힘들어지고 권한을 나누는 과정에서 새로운 도전에 부딪히는데, 이는 대기업이 사업부제를 도입할 때 겪는 어려움과 비슷하다.

이번 편은 거대한 하나의 구조가 한계에 부딪히고, 그 책임을 나누는 과정에서 시스템이 무엇을 얻고 무엇을 잃는지를 다룬다.


1편은 영속성의 비용, 2편은 인덱스의 양면, 3편은 트랜잭션에서의 정합성과 동시성 균형을 다루었다. 모두 컴퓨터 한 대라는 제한된 공간 안에서 자원 경합을 제어하고 성능을 최적화했다.

하지만 서비스가 성장하면 어느 순간부터 문제의 성격 자체가 바뀐다. 이제는 락 전략이나 인덱스 최적화만으로 해결되지 않는다. 디스크는 가득 차고, 메모리는 한계에 달한다. 시스템은 더 이상 “어떻게 효율적으로 처리할 것인가”가 아니라, 이 집중된 책임 자체를 어떻게 분산할 것인가를 고민한다.

서비스 초기에는 트래픽이 적어서 서버 하나로도 충분히 감당할 수 있다. 모든 데이터를 한 DB에 모아두는 게 가장 간단하고 효율적이기도 하다. 개발도 빠르고 데이터 정합성도 지키기 쉽다. 그런데 서비스 규모가 커지고, 사용자가 폭발적으로 늘어난다면 도저히 이대로는 버틸수 없는 상황에 직면한다.

DB에 걸리는 부하를 낮추기 위해 당장 무엇을 할 수 있을까? 우선 스펙을 올린다. CPU는 더 빠른 걸로 갈아 끼우고, 메모리는 여유롭게 더 꽂는다. 하지만 이런 수직 확장(Scale-up)은 무한정 진행할 수 없다. 아무리 비싼 장비라도 물리적 제한은 있기 마련이다. 클라우드가 제공하는 가장 큰 인스턴스와 물리 서버또한 마찬가지다.

하지만 진짜 문제는 따로 있다. 트래픽이 계속 늘어나면, 이 한 대의 DB가 모든 요청을 감당하는 데 필요한 비용이 기하급수적으로 뛴다. 읽기와 쓰기 요청이 한 대에 몰리고, 각종 트랜잭션과 장애 대응도 한 서버가 전담해야 한다. 마치 본사에 모든 승인이 밀리는 중앙 집권 체제 같은 구조다.

이 구조에서 단일 장애점(SPOF)은 피할 수 없다. 아무리 좋은 서버라도 한 순간 정지하면 전체 서비스가 즉시 중단된다.

스펙을 한 단계 올릴 때마다 비용은 결코 선형적으로 증가하지 않는다. 성능을 두 배 올리려고 하면 비용은 그 이상으로 많이 든다. 실제 AWS의 메모리 최적화 인스턴스(r6i 계열) 비용만 봐도 사양이 좋아질수록 감당해야 하는 금액이 가파르게 상승한다.

  • 32GB (r6i.xlarge) : 월 약 $200
  • 64GB (r6i.2xlarge) : 월 약 $363
  • 128GB (r6i.4xlarge) : 월 약 $726
  • 256GB (r6i.8xlarge) : 월 약 $1,452

단순히 비싼 장비를 들인다고 운영 효율과 안정성까지 비례해서 좋아질까? 절대 아니다. 오히려 최고 사양의 고가 장비 한 대에 모든 읽기, 쓰기, 트랜잭션, 장애 대응이라는 모든 책임이 집중된다.

128GB짜리 거대한 단일 서버 한 대를 유지하는 비용($726)과, 32GB짜리 중소형 서버 4대를 사서 똑같이 총합 128GB 환경을 맞추는 비용($800)은 단순 숫자로 보면 비슷해 보인다. 물론 운영 복잡도는 증가하지만, 확장성과 장애 대응 측면에서는 여러 대로 분산하는 편이 훨씬 유리하다.

따라서 임계점에 도달하면 시스템은 하나의 고사양 DB 말고, 여러 개의 저사양 DB를 써야 할 때라는 신호다. 바로 수평 확장(Scale-out)이 필요한 시점이 온다.

Reference: AWS EC2 R6i Pricing


서버가 터지기 직전이라고 해서 처음부터 데이터를 분할하지는 않는다. 관리가 너무 힘들고 복잡해지기 때문이다. 보통은 중심 데이터베이스(DB)의 부담을 덜어주는 것부터 시작한다.

여기서 직면하는 문제는 단순히 처리량이 많아서가 아니다. 사장 한 명에게 성격이 다른 두 가지 업무가 한꺼번에 몰려 혼선이 생기기 때문이다. 회사의 미래가 걸린 ‘중요한 계약서 작성(쓰기)‘을 하느라 바쁜데, 손님들이 “가게 문 열었나요?”, “이 상품 얼마예요?” 같은 ‘단순한 문의(읽기)‘를 수천 번씩 던지니 마비가 오는 것이다.

Replication - 본사 내의 읽기 권한 위임

섹션 제목: “Replication - 본사 내의 읽기 권한 위임”

시스템에 가해지는 부하를 효율적으로 줄이려면 기존 틀을 유지한 채 역할에 따라서 일을 나누면 된다. 이러한 분업이 가능한 이유는 읽기 작업이 전체 트래픽의 90% 이상을 차지하기 때문이다. 따라서 쓰기 작업과 읽기 작업을 분리하면, 시스템에 가해지는 부하를 덜어낼 수 있다.

이를 바탕으로 설계된 것이 바로 “최종 결정권은 사장(Master)이 맡고, 기타 안건은 직원(Replica)들에게 맡기자”는 원칙이다.

데이터가 틀리면 큰일 나는 생성, 수정, 삭제는 딱 한 대의 Master DB가 전담하여 관리한다. 반면 부하의 대부분을 차지하는 읽기 작업은 여러 대의 Replica DB가 처리하는 것이다.

[Replication]
WRITE PATH (single source)
┌───────────┐
│ MASTER │
│ (write) │
└─────┬─────┘
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ REPLICA 1 │ │ REPLICA 2 │ │ REPLICA 3 │
│ (read) │ │ (read) │ │ (read) │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└──── replication lag (time delay) ────┘
→ stale reads possible

하지만 권한을 넘기는 순간 필연적인 대가가 따른다.

한 상황을 가정해보자. 주문과 결제를 끝낸 한 손님은 이상한 경험을 한다. 방금 본사에 결제 서류를 제출해 처리가 완료되었는데, 곧바로 안내 데스크로 가서 “제 주문 확인해 주세요”라고 하니 직원이 “아직 접수된 내역이 없는데요?”라고 답하는 상황이다. 결제는 본사(Master)에서 완벽하게 끝났지만, 안내 데스크(Replica)의 화면에는 아직 본사의 공유 지시가 도착하지 않았기 때문이다.

이처럼 Master에서 처리된 최신 데이터가 Replica에 복사되어 반영될 때까지 발생하는 미세한 시간 차를 Replication Lag라고 부른다.

현장의 시차를 단 1초도 허용하지 않으려고 고집할 수도 있다. 사장이 계약서를 쓸 때마다 모든 하부 직원에게 확인을 받은 뒤 다음 업무를 처리하는 방식이다. 이렇게 하면 정보는 실시간으로 완벽히 일치한다. 하지만 직원이 늘어날수록 사장의 대기 시간만 늘어나 분업의 의미가 사라진다.

따라서 대다수의 아키텍처는 현실적인 타협점을 찾는다. 데이터 정합성이 절대적인 생성, 수정, 삭제 처리는 부하를 감수하더라도 최신 원본이 있는 Master에서 직접 읽어오도록 강제한다. 반면, 몇 초쯤 늦게 반영되어도 지장이 없는 일반 읽기 처리는 Replica로 돌리는 방식이다.

이처럼 모든 서버의 데이터가 “시간이 흐르면 결국 다 똑같아진다”고 타협하는 기술적 규칙을 최종 정합성(Eventual Consistency)이라고 부른다. 이는 당장 눈앞의 1초짜리 일치보다, 전체 시스템이 마비되지 않고 수많은 사용자를 감당할 수 있도록 확장성을 선택하는 판단인 셈이다.


앞서 본 Replication 구조는 직원들이 안내(Read)를 대신해 주면서 사장(Master)의 부담을 덜어주는 좋은 전략이었다. 하지만 치명적인 한계가 있다. 계약서 도장을 찍는 ‘쓰기(Write)’ 업무는 여전히 사장 혼자 감당해야 하고, 회사의 모든 서류는 사장실에 있는 단 하나의 서류함(단일 서버의 디스크)에 다 들어가야 한다는 점이다.

서비스가 성장해 쓰기가 초당 수만 건을 넘어서거나, 데이터 총량이 단일 서버의 디스크 용량을 초과하면 Replication만으로는 유지하기 어렵다. 이때는 본사 건물을 허물고 전국에 완전히 독립된 별개의 지사들을 차려, 서류 양을 적절히 분산해야 한다. 이것이 바로 샤딩(Sharding)이다.

이제 진짜 문제는 “어떤 기준으로 손님을 지사로 쪼개 보낼 것인가”다. 이 기준(Shard Key)에 따라 시스템의 향방과 비용이 완전히 달라진다.

어떻게 해야 데이터를 모든 서버에 균등하게 분배할 수 있을까? 특정 서버에만 데이터가 몰려 시스템이 무너지는 리스크를 구조적으로 차단하기 위해 고안된 것이 바로 해시(Hash) 샤딩이다.

[ Hash 샤딩 ]
key
┌─────────────┐
│ hash(key) │
└──────┬──────┘
┌───────────┼───────────┬───────────┐
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ Node A │ │ Node B │ │ Node C │ │ Node D │
└────────┘ └────────┘ └────────┘ └────────┘
order LOST (no global ordering)
random distribution

이 방식은 자원을 고르게 사용하여 비용 예측의 안정성을 주지만, 대가로 ‘데이터의 연속성’을 잃는다. 방금 연달아 가입한 100번 회원과 101번 회원이 전혀 다른 서버로 흩어지기 때문이다.

만약 비즈니스에서 “최근 가입한 유저 100명 명단” 같은 범위 조회를 요청하면 시스템은 큰 비효율을 마주한다. 데이터가 순서 없이 사방에 찢어져 있어 모든 서버를 다 뒤져 긁어모은 뒤 본사에서 다시 정렬해야 하기 때문이다. 결국 해시 샤딩은 기계적 균등을 얻는 대신 정렬된 데이터 조회 성능을 완전히 희생한 전략이다.

해시 샤딩이 지워버린 ‘순서와 비즈니스 맥락’을 복구하려는 목적에서 등장한 전략이 바로 Range(범위) 샤딩이다.

현업에서는 특정 기간의 결제 내역이나 알파벳순 고객 명단처럼 데이터를 순서대로 엮어 읽어야 하는 요구사항이 끊임없이 발생한다. 이에 맞추어 서버를 날짜나 범위별로 쪼개 관리하면, 조회가 필요한 해당 서버 한 곳만 찾아가 순서대로 읽어 내릴 수 있어 탐색 효율을 극대화할 수 있다.

[ Range 샤딩 ]
key range
┌──────────┬──────────┬──────────┬──────────┐
│ Node A │ Node B │ Node C │ Node D │
│ 0-25% │ 25-50% │ 50-75% │ 75-100% │
└────┬─────┴────┬─────┴────┬─────┴────┬─────┘
│ │ │ │
│ │
│ HOTSPOT REGION
│ (latest range)
└────────────────────────────────────────▶
skewed traffic → uneven load distribution

그러나 현실의 트래픽은 균등하지 않다. 트래픽이라는 손님은 과거가 아닌 언제나 ‘현재’에 몰린다. 오늘 들어온 주문, 지금 올라온 게시물, 방금 가입한 유저에게 모든 트래픽이 집중된다. 따라서 가장 최신 달을 담당하는 서버 한 대에 쓰기 요청이 폭주하고, 과거 데이터를 담당하는 서버들은 한가해진다.

다시 말해 범위 샤딩은 순서를 보존한 대가로 부하의 쏠림(Hotspot)을 감수해야한다.

디스코드 사례를 다시 한 번 보자. 데이터 용량은 비슷해도 인플루언서 방의 트래픽이 압도적이다. 결국 B 서버만 과부하로 뻗는다. 이 현상이 Hot Shard다.

Hash 샤딩은 데이터를 균등하게 나누었으나, 순서를 잃었다. Range 샤딩은 순서를 보존했지만, 최신 데이터로 트래픽이 몰렸다.

하지만 Hot Shard는 그보다 더 근본적인 현실을 드러낸다. 데이터가 아무리 분산되어 있어도, 현실 트래픽은 일정하지 않다는 점이다.

이 시점부터 아키텍처의 목표도 바뀐다. 이제 중요한 것은 데이터를 어떻게 나눌지가 아니다. 폭발적인 요청이 하나의 샤드로 집중되는 현상을 어떻게 흩뜨릴 것인가다.

복합 키 — 더 잘게 나누기

가장 직관적인 반응은 여전히 분배의 논리 안에 있다. 데이터가 한곳에 뭉쳐서 문제라면, 나누는 단위를 더 세밀하게 조정하면 되지 않을까?

기존 Shard Key에 시간이나 숫자를 조합한 복합 키를 적용해, 하나의 거대한 핫스팟을 여러 샤드로 파편화하는 방식이다.

[ Before ] [ After (복합 키) ]
인기 게시물 123 인기 게시물 123
│ ┌───────┼───────┐
▼ ▼ ▼ ▼
[ Shard A ] [A-1] [A-2] [A-3]
████████████████ █████ █████ █████
모든 요청 집중 부하가 여러 샤드로 분산됨

이 방법도 완전하진 않았다. 데이터를 아무리 잘게 찢어도, 사용자의 관심 자체는 여전히 같은 대상에 집중되기 때문이다.

캐시 계층 — 도달하지 못하게 막기

여기서 사고가 한 단계 전환된다. 분배를 더 잘하는 게 아니라, 폭주하는 요청이 데이터베이스까지 도달하는 것 자체를 줄이자는 발상이다.

앞단에 캐시 계층을 배치해 조회 결과를 몇 초만 붙잡아 두면, 동일한 요청 수만 건이 DB에 닿기 전에 흡수된다. 시스템은 더 이상 모든 요청을 원본 데이터베이스까지 내려보내지 않는다.

정상 상황
요청 ██████████████ ──▶ [ Cache ] ═══════════▶ 응답
└──── 일부만 ───▶ [ DB ]

하지만 캐시도 완벽하지 않다. 캐시가 만료되는 순간, 대기하던 요청이 한꺼번에 원본 DB로 쏟아지는 Cache Stampede 문제가 다시 발생한다.

캐시 만료
요청 ██████████████ ──▶ [ Cache ] ── MISS ──┬──▶ [ DB ]
├──▶ [ DB ]
├──▶ [ DB ]
├──▶ [ DB ]
└──▶ [ DB ]
Cache Stampede

요청 병합 — 요청 자체를 줄이기

디스코드가 도달한 해답은 더 근본적이었다. 동시에 밀려드는 동일 요청을 개별 작업으로 처리하지 않는 것이다.

짧은 시간 동안 동일 요청들을 큐에 묶고, 단 한 번만 쿼리를 실행한 뒤 그 결과를 모든 요청에 공유한다. 수만 개의 요청 흐름을 하나의 연산으로 압축해 버리는 방식이다.

요청 A ─┐ ┌─▶ 응답 A
요청 B ─┼─┐ ┌─┼─▶ 응답 B
요청 C ─┘ │ │ └─▶ 응답 C
├──────▶ Query × 1 ───────┤
요청 D ─┐ │ │ ┌─▶ 응답 D
요청 E ─┼─┘ └─┼─▶ 응답 E
요청 F ─┘ └─▶ 응답 F

샤딩은 데이터를 얼마나 균등하게 저장했는지가 아니라, 실제 요청 패턴을 얼마나 현실적으로 예측했는가에 달려 있다. 흐름에 맞게 분할 기준을 설계할 줄 알아야 변동성 많은 서비스를 안정적으로 운영할 수 있다.


중앙 통제가 부르는 병목은 데이터베이스에만 국한되지 않는다. 거대 기업 조직 역시 규모가 커지면 똑같은 한계에 부딪힌다.

1960년대 미국의 거대 자동차 기업 GM이 위와 같은 사례에 해당된다. 비즈니스가 폭발적으로 커지자 지방 대리점의 사소한 조율부터 신차 기획까지, 모든 의사결정이 본사 사장실을 통과해야 했다. 사장실 책상 위에는 서류가 산더미처럼 밀렸고 결재는 끝없이 늦어졌다. 조직 전체가 ‘승인 병목’으로 멈춰 선 것이다.

경영학자 알프레드 챈들러는 이 현상을 분석하며 유명한 명제를 남겼다. “조직의 구조는 기업이 선택한 전략을 따른다(Structure follows strategy).” 시장이 커졌다면 조직의 판을 새로 짜야지, 사장 한 명의 개인 능력(Scale-up)을 업그레이드해서 버티는 것은 구조적으로 불가능하다는 뜻이다.

Reference: Alfred Chandler, “Strategy and Structure” (1962)

결국 GM은 사장의 실시간 간섭을 끊고, 각 브랜드 사업부장에게 현장 대응의 전권을 넘기는 ‘사업부제’를 도입했다. 본사는 장기 전략만 짜고, 일상적인 운영 판단은 각 지사가 독립적으로 내리게 한 것이다. 우리가 지금까지 살펴본 기술의 여정도 이 조직 개편과 정확히 일치한다.

  • Replication (복제): 사장의 업무 중 리스크가 낮은 ‘단순 안내(Read)’ 권한을 직원에게 넘긴 것
  • Sharding (분할): 사장실 서류함만으론 감당이 안 돼서 전국에 ‘독립 지사’를 차린 것

하지만 여기서 실수가 일어나기 쉽다. 지사를 전국에 찢어놓고도, 손님 한 명을 받을 때마다 “옆 지사 장부 확인해라”, “본사 도장 받아와라” 하며 일 처리가 지연되고만다. 이런 구조를 분산 트랜잭션 혹은 분산 락이라고 한다. 이렇게 하면 데이터는 일치할지 몰라도, 속도는 단일 서버보다 훨씬 더 느려진다. 겉만 분산이지, 속은 ‘위장된 중앙집권’이기 때문이다.

진짜 분산은 서버만 여러 대 늘어놓는 인프라 기술이 아니다. 각 서버가 다른 서버의 도움이나 허락 없이도, 자기에게 온 손님을 판단하고 즉각 돌려보낼 수 있게 서비스의 경계를 깔끔하게 잘라내는 일이다.

지사에서 “나중에 본사랑 맞추면 되니 일단 직원 선에서 안내해 드려”라고 판단하는 최종 정합성(Eventual Consistency)을 수용하거나, 분할된 독립 지사(Shard)가 “이 구역 손님은 내가 책임지고 계약 도장 찍어줄게”라고 독자적으로 행동할 수 있어야 비로소 분산의 가치가 살아난다.


사장의 스펙을 올리는 데는 명확한 천장이 있고(Scale-up), 단순 업무는 밑으로 위임해야 하며(Replication), 임계점이 오면 지사로 독립시켜야 한다(Sharding).

분산이 어려운 진짜 이유는 기술 부족이 아니다. 권한을 넘긴 뒤 생기는 약간의 지연과 데이터 어긋남을 시스템 차원에서 감수할 준비가 안 되어 있기 때문이다. 분산 시스템의 본질은 서버를 늘리는 것이 아니라, 통제권을 어디까지 독립적으로 위임할 수 있는가의 문제다.

다음 편: 폭발하는 트래픽을 감당하기 위해 엄격한 정합성을 내려놓은 NoSQL캐시(Cache)를 다룬다. 실시간 일치라는 제약을 유연하게 포기하는 기술적 타협이 어떻게 대규모 시스템을 지탱하는지 알아보자.