데이터베이스는 보통 한 대의 서버에서 시작한다. 서비스가 작을 때는 이걸로 충분하다. 그런데 데이터가 쌓이고 트래픽이 늘면 언젠가 한 대로는 감당이 안 되는 순간이 온다. 디스크가 가득 차거나, 초당 쿼리 수가 한계를 넘거나, 메모리에 인덱스가 다 안 올라가는 식이다.

가장 먼저 떠오르는 해법은 더 좋은 서버로 갈아타는 것이다. CPU와 메모리, 디스크를 키우는 이 방식을 수직 확장(scale-up)이라 한다. 단순하지만 한계가 분명하다. 한 대가 가질 수 있는 사양에는 천장이 있고, 사양이 올라갈수록 가격이 가파르게 오른다. 그래서 일정 규모를 넘으면 한 대를 키우는 대신 여러 대에 나눠 담는 수평 확장(scale-out)으로 방향을 튼다. 샤딩은 이 수평 확장을 데이터베이스에 적용한 기법이다.

샤딩이란

샤딩(sharding)은 하나의 큰 데이터셋을 여러 조각으로 쪼개 서로 다른 서버에 나눠 저장하는 것이다. 이때 각 조각을 샤드(shard)라 부른다. 도서관 장서가 너무 많아져 건물 하나에 다 안 들어갈 때, 책을 주제별로 나눠 여러 건물에 분산해 보관하는 것과 같다. 한 건물에 모든 책이 있지는 않지만, 어떤 책이 어느 건물에 있는지 규칙만 알면 찾아갈 수 있다.

데이터베이스 용어로는 수평 분할(horizontal partitioning)이라고도 한다. 테이블을 가로로 잘라 행(row)들을 여러 서버에 흩뿌리기 때문이다. 사용자 1번부터 100만 번까지의 데이터가 한 테이블에 있었다면, 샤딩 후에는 150만은 A 서버에, 50만100만은 B 서버에 나뉘어 들어간다. 각 서버는 전체의 일부만 책임지므로, 데이터와 부하가 분산된다.

복제와 무엇이 다른가

비슷해 보이는 복제(replication)와 헷갈리기 쉽지만 목적이 다르다. 복제는 같은 데이터를 여러 서버에 똑같이 복사해 두는 것이다. 한 대가 죽어도 다른 대가 같은 데이터를 갖고 있어 가용성이 높아지고, 읽기 요청을 여러 복제본에 분산할 수 있다. 하지만 데이터 전체가 한 대에 다 들어가야 한다는 한계는 그대로다. 모든 서버가 전부를 갖고 있으니까.

샤딩은 반대다. 각 서버가 데이터의 서로 다른 부분만 갖는다. 그래서 한 대에 다 안 들어가는 데이터도 담을 수 있고, 쓰기 부하까지 분산된다. 실제 시스템은 둘을 함께 쓴다. 데이터를 여러 샤드로 나누고(샤딩), 각 샤드를 다시 여러 벌 복제해(복제) 분산과 가용성을 모두 얻는 식이다.

복제샤딩
각 서버가 가진 것전체 데이터의 사본데이터의 일부 조각
주된 목적가용성, 읽기 분산용량·쓰기 부하 분산
데이터 총량 한계한 대 용량에 묶임서버를 늘려 확장

샤드 키 — 무엇을 기준으로 나누나

샤딩의 핵심 결정은 “어떤 기준으로 데이터를 쪼갤 것인가"이다. 이 기준이 되는 값을 샤드 키(shard key)라 한다. 사용자 ID, 지역, 주문 번호 같은 것이 샤드 키가 될 수 있다. 어떤 행이 어느 샤드로 갈지는 이 키 값으로 결정된다.

샤드 키를 정하면, 그 키로 어떤 샤드를 고를지 정하는 규칙이 필요하다. 대표적으로 세 가지 방식이 있다.

범위 기반(range)은 키 값의 구간으로 나눈다. 사용자 ID 150만은 샤드 A, 50만100만은 샤드 B 하는 식이다. 직관적이고 범위 조회에 유리하지만, 특정 구간에만 요청이 몰리면 그 샤드만 과부하가 된다.

해시 기반(hash)은 키 값을 해시 함수에 넣어 나온 값으로 샤드를 정한다. 키를 고르게 흩뿌려 부하가 균등해지지만, 연속된 키들이 서로 다른 샤드로 흩어져 범위 조회가 어려워진다.

디렉토리 기반(directory)은 “어떤 키가 어느 샤드에 있는지"를 별도의 조회 테이블에 두고 매번 찾아본다. 유연하게 재배치할 수 있지만, 이 조회 테이블 자체가 모든 요청이 거쳐 가는 병목이자 단일 장애점이 될 수 있다.

요청이 들어오면 이 규칙에 따라 올바른 샤드로 보내야 하는데, 이 길 안내 역할을 라우팅(routing)이라 한다. 애플리케이션이 직접 계산하거나, 중간에 라우팅 계층을 두거나, 데이터베이스가 알아서 처리하기도 한다.

무엇을 감수하나

샤딩은 확장성을 주는 대신 복잡성을 가져온다.

가장 흔한 문제는 핫스팟(hotspot)이다. 데이터나 트래픽이 특정 샤드에 쏠리는 현상으로, 샤드 키를 잘못 고르면 발생한다. 인기 상품 하나에 주문이 폭주하는데 그 상품이 한 샤드에 있다면, 서버를 아무리 늘려도 그 샤드만 불탄다. 부하를 고르게 분산하는 샤드 키를 고르는 것이 그래서 중요하다.

둘째는 리샤딩(resharding)이다. 샤드를 더 늘리거나 줄여 데이터를 재배치하는 일인데, 운영 중인 시스템에서 데이터를 옮기는 것은 위험하고 까다롭다. 단순 해시 방식은 샤드 수가 바뀌면 거의 모든 키의 위치가 달라져 대규모 이동이 필요하다. 이 이동량을 줄이려고 일관된 해싱(consistent hashing) 같은 기법을 쓴다.

셋째는 교차 샤드 연산이다. 서로 다른 샤드에 흩어진 데이터를 한꺼번에 다뤄야 하는 조인이나 트랜잭션은 어렵고 느리다. 한 건물 안에서 끝나던 일이 여러 건물을 오가는 일이 되기 때문이다. 그래서 샤딩 설계는 “함께 조회되는 데이터는 같은 샤드에 두도록” 샤드 키를 고르는 데 많은 공을 들인다.

언제 선택하나

샤딩은 공짜가 아니다. 라우팅, 리샤딩, 교차 샤드 연산이라는 복잡성이 따라오므로, 한 대로 버틸 수 있다면 굳이 먼저 도입할 이유가 없다. 보통은 수직 확장과 복제, 캐시, 읽기 분산을 먼저 시도해 보고, 그래도 한 대의 쓰기 용량이나 저장 한계에 부딪힐 때 마지막 카드로 꺼낸다.

그럼에도 일정 규모를 넘어선 대규모 서비스는 결국 샤딩을 피하기 어렵다. 단일 서버의 천장은 분명하고, 수평 확장만이 그 천장을 넘는 길이기 때문이다. 핵심은 “샤딩이 필요해지기 전에 샤드 키를 신중히 고르는 것"이다. 한번 정한 샤드 키를 바꾸는 일은 데이터 전체를 다시 배치하는 것과 같아, 가장 되돌리기 어려운 결정에 속한다.