샤딩은 데이터를 여러 노드로 분할해 데이터베이스를 확장하지만 라우팅, 리밸런싱, 새로운 실패 모드를 도입해 시스템을 이해하고 운영하기 어렵게 만듭니다.

샤딩(수평 파티셔닝이라고도 함)은 애플리케이션에겐 하나로 보이는 데이터베이스의 데이터를 여러 머신(샤드)으로 나누는 것을 의미합니다. 각 샤드는 행의 일부만 보관하지만, 함께 모이면 전체 데이터셋을 구성합니다.
도움이 되는 사고 모델은 논리적 구조와 물리적 배치의 차이입니다.
애플리케이션 관점에서 쿼리는 하나의 테이블처럼 실행되길 원합니다. 내부적으로는 시스템이 어떤 샤드에 접속할지 결정해야 합니다.
샤딩은 복제와 다릅니다. 복제는 동일한 데이터를 여러 노드에 복사해 주로 가용성과 읽기 확장을 담당합니다. 샤딩은 데이터를 분할해 각 노드가 다른 레코드를 보관합니다.
또한 수직 확장(더 큰 기계로 이전해 CPU/RAM/디스크를 늘리는 것)과도 다릅니다. 수직 확장은 간단할 수 있지만 한계와 비용 문제가 있습니다.
샤딩은 용량을 늘리지만 데이터베이스의 모든 쿼리를 자동으로 빠르게 만들거나 운영을 단순화하지는 않습니다.
따라서 샤딩은 스토리지와 처리량을 수평으로 확장하는 수단으로 이해해야지, 모든 면에서 무료 업그레이드는 아닙니다.
샤딩은 보통 첫 선택이 아닙니다. 성공적으로 성장한 시스템이 물리적 한계에 부딪히거나 운영상의 고통이 잦아질 때 도입됩니다. 동기는 ‘샤딩을 하고 싶다’가 아니라 ‘한 DB가 단일 장애 지점이자 비용 중심이 되는 걸 피하고 싶다’입니다.
단일 DB 노드는 여러 방식으로 한계에 이를 수 있습니다:
이 문제가 반복되면 흔히 원인은 ‘한 머신이 너무 많은 책임을 지고 있다’는 것입니다.
데이터베이스 샤딩은 데이터를 여러 노드로 나눠 머신을 추가하면서 용량을 키우게 해줍니다. 잘 하면 워크로드를 격리해(한 테넌트의 스파이크가 다른 이의 지연을 망치지 않게) 비용을 통제할 수 있습니다.
반복되는 패턴은 피크 시 p95/p99 지연 상승, 복제 지연 증가, 백업/복구 시간이 허용 창을 초과, 사소한 스키마 변경이 큰 이벤트가 되는 경우 등입니다.
팀은 보통 샤딩 전 더 간단한 옵션을 다 사용해봅니다: 인덱싱과 쿼리 개선, 캐싱, 리드 레플리카, 단일 DB 내 파티셔닝, 오래된 데이터 아카이빙, 하드웨어 업그레이드 등. 샤딩은 확장을 해결하지만 조정, 운영 복잡성, 새로운 실패 모드를 함께 가져오므로 결정 기준이 높아야 합니다.
샤딩된 데이터베이스는 하나의 엔진이 아니라 협력하는 작은 시스템입니다. 샤딩이 “이해하기 어렵다” 느껴지는 이유는 성능과 정합성이 DB 엔진뿐 아니라 이 구성요소들의 상호작용에 달려 있기 때문입니다.
샤드는 보통 자체 서버나 클러스터에 저장되는 데이터의 부분집합입니다. 각 샤드는 일반적으로:
애플리케이션 관점에서 샤딩된 구성은 하나의 논리 DB처럼 보이려 합니다. 그러나 단일 노드에서는 “인덱스 조회 하나”로 끝나던 쿼리가 샤딩에서는 “적절한 샤드를 찾고, 그 다음 조회”로 바뀝니다.
라우터(코디네이터, 쿼리 라우터, 프록시라고도 함)는 교통정리 역할을 합니다. 실무적 질문에 답합니다: 이 요청은 어떤 샤드가 처리해야 하나?
두 가지 일반적 패턴이 있습니다:
라우터는 앱 복잡도를 줄여주지만, 잘못 설계되면 병목이나 새로운 장애 지점이 될 수 있습니다.
샤딩은 다음과 같은 메타데이터에 의존합니다:
이 정보는 설정 서비스(혹은 작은 컨트롤 플레인 DB)에 저장됩니다. 메타데이터가 오래되거나 불일치하면, 샤드들이 완전히 건강하더라도 라우터가 잘못된 곳으로 트래픽을 보낼 수 있습니다.
샤딩은 시간이 지나도 시스템을 유지하게 하는 백그라운드 프로세스에 의존합니다:
이 작업들은 초기에 무시하기 쉽지만, 운영 중 시스템의 형태를 바꾸기 때문에 많은 프로덕션 사고의 진원지입니다.
샤드 키는 행/문서를 어느 샤드에 저장할지 결정하는 필드입니다. 이 한 가지 선택이 나중에 성능, 비용, 기능 편의성에 큰 영향을 줍니다—요청이 단일 샤드로 라우팅되는지 아니면 많은 샤드로 퍼지는지를 결정하기 때문입니다.
좋은 키는 보통 다음을 가집니다:
user_id는 country보다 낫다)예: 멀티테넌트 앱에서 tenant_id로 샤딩하면 한 테넌트의 대부분 읽기/쓰기가 한 샤드에 머물러 로컬 처리가 가능하고, 테넌트가 많으면 부하를 분산시킬 수 있습니다.
일부 키는 문제를 보장합니다:
심지어 저카디널리티 키가 필터링에는 편리해 보여도 관련 행들이 모든 샤드에 흩어져 스캐터-개더 쿼리가 되어버립니다.
부하 균형에 가장 좋은 샤드 키가 항상 제품 쿼리 관점에서 최선은 아닙니다.
user_id)에 맞춘 키를 고르면 일부 글로벌 쿼리(관리자 리포팅 등)가 느려지거나 별도 파이프라인이 필요할 수 있습니다.region)를 고르면 핫스팟과 불균등 용량 문제가 생길 수 있습니다.대부분 팀은 가장 빈번하고 지연에 민감한 작업에 맞춰 샤드 키를 최적화하고, 나머지는 인덱스, 디노멀라이제이션, 레플리카 또는 전용 분석 테이블로 처리합니다.
최고의 방법은 없습니다. 어느 전략을 택하느냐에 따라 라우팅의 용이성, 데이터 분포의 균일성, 그리고 어떤 접근 패턴이 문제를 일으키는지가 결정됩니다.
레인지 샤딩은 각 샤드가 키 공간의 연속 구간을 소유합니다—예:
해시 샤딩은 샤드 키에 해시 함수를 적용해 샤드를 선택합니다. 일반적으로 데이터가 더 고르게 분포되어 ‘최신 샤드로 몰림’ 문제를 피할 수 있습니다.
트레이드오프: 레인지 쿼리가 비싸집니다—예: ID X와 Y 사이 고객 조회가 많은 샤드를 건드릴 수 있습니다.
실무에서 자주 간과되는 점은 일관된 해싱(consistent hashing)입니다. 샤드 수에 바로 매핑하면 샤드 추가 시 전체가 재배치되므로, 많은 시스템이 해시 링과 ‘가상 노드’를 사용해 용량 추가 시 일부 키만 이동하게 합니다.
디렉터리 샤딩은 키 → 샤드 위치의 명시적 매핑(룩업 테이블/서비스)을 저장합니다. 가장 유연해 특정 테넌트를 전용 샤드에 두거나 한 고객만 이동시키는 등 세밀한 제어가 가능합니다.
단점은 추가 의존성입니다. 디렉터리가 느리거나 오래되거나 사용할 수 없으면 라우팅이 영향을 받아 샤드들이 건강해도 요청이 잘못 전달될 수 있습니다.
실제 시스템은 종종 방법을 섞습니다. 복합 샤드 키(예: tenant_id + user_id)는 테넌트를 분리하면서 테넌트 내에서 부하를 분산합니다. 서브샤딩도 비슷합니다: 먼저 테넌트로 라우팅한 뒤 해당 테넌트 그룹 내에서 해시로 분산시켜 ‘큰 테넌트’가 단일 샤드를 지배하는 걸 방지합니다.
샤딩된 DB에는 매우 다른 두 가지 쿼리 경로가 있습니다. 어떤 경로에 놓이는지 아는 것이 성능의 대부분의 놀라움을 설명하고, 샤딩이 왜 예측하기 어려운지 알려줍니다.
이상적인 결과는 쿼리를 정확히 한 샤드로 라우팅하는 것입니다. 요청에 샤드 키가 포함되거나 라우터가 매핑할 수 있으면 시스템은 바로 해당 샤드로 보낼 수 있습니다.
그래서 팀들은 흔한 읽기 작업을 “샤드-키 인지”로 만들기 위해 집착합니다. 한 샤드는 네트워크 왕복이 적고 실행이 단순하며 잠금과 조정이 적어 훨씬 낮은 지연을 제공합니다.
쿼리를 정확히 라우팅할 수 없으면(예: 비샤드키 필드로 필터링할 때) 시스템은 여러 혹은 모든 샤드에 브로드캐스트할 수 있습니다. 각 샤드는 로컬에서 쿼리를 실행하고 라우터/코디네이터가 결과를 병합—정렬, 중복 제거, LIMIT 적용, 부분 집계 결합 등을 수행합니다.
이 팬아웃은 꼬리 지연을 증폭합니다: 9개의 샤드가 빠르게 응답해도 하나의 느린 샤드가 전체 요청을 가로챕니다. 또한 부하가 증폭됩니다: 하나의 사용자 요청이 N개의 샤드 요청이 됩니다.
샤드 간 조인은 데이터가 원래 DB 안에서 만나 처리되는 대신 샤드들 사이로 이동해야 하므로 비용이 큽니다. 간단한 집계(COUNT, SUM, GROUP BY)도 각 샤드에서 부분 결과를 계산한 후 병합하는 2단계 계획이 필요합니다.
대부분 시스템은 기본적으로 로컬 인덱스를 사용합니다: 각 샤드는 자체 데이터만 인덱싱합니다. 유지비는 적지만 라우팅에는 도움이 되지 않아 여전히 스캐터가 발생할 수 있습니다.
글로벌 인덱스는 비-샤드키 필드로도 타깃 라우팅을 가능하게 하지만, 쓰기 오버헤드와 추가 조정, 확장성과 일관성 문제를 유발합니다.
쓰기에서 샤딩은 단순한 ‘확장’ 이상으로 느껴집니다. 한 샤드만 건드리면 빠르고 단순하지만, 여러 샤드를 건드리면 느리고 오류에 취약하며 정확성을 보장하기 어려워집니다.
각 요청이 정확히 한 샤드로 라우팅되면(보통 샤드 키 덕분) DB는 정상적인 트랜잭션 메커니즘을 사용합니다. 샤드 내 원자성·격리가 보장되고 운영 이슈는 익숙한 단일 노드 문제처럼 보입니다—그저 N번 반복되는 것뿐입니다.
논리적 동작 하나가 두 샤드의 데이터를 수정해야 한다면(예: 송금, 주문을 다른 고객으로 이동, 다른 곳에 저장된 집계 업데이트) 분산 트랜잭션 영역에 들어갑니다.
분산 트랜잭션은 느린 노드, 파티션, 재시작 가능한 머신들 간의 조정을 요구하므로 어렵습니다. 2단계 커밋 같은 프로토콜은 추가 왕복을 만들고 타임아웃 시 차단될 수 있으며 실패 시 애매함을 남깁니다: 샤드 B가 변경을 적용하기 전에 코디네이터가 죽었는가? 클라이언트가 재시도하면 중복 적용되는가? 재시도하지 않으면 잃어버리는가?
다음 전술들이 멀티 샤드 트랜잭션 필요성을 줄입니다:
샤딩 시스템에서는 재시도가 피할 수 없는 일이므로 쓰기를 아이덤포텐트하게 설계해야 합니다. 안정된 오퍼레이션 ID(예: idempotency key)를 사용하고 DB에 “이미 적용됨” 마커를 저장하면, 타임아웃 후 클라이언트가 재시도해도 두 번째 시도는 no-op가 되어 중복 청구나 일관성 문제를 피할 수 있습니다.
샤딩은 데이터를 여러 머신으로 나누지만 중복(복제)은 여전히 필요합니다. 복제는 샤드가 노드 실패 시 가용성을 유지하게 하고, 동시에 “지금 당장 무엇이 진실인가?”를 복잡하게 만듭니다.
대부분 시스템은 각 샤드 내부에서 복제합니다: 하나의 프라이머리가 쓰기를 받고 한두 개 이상의 레플리카가 변경을 복사합니다. 프라이머리가 실패하면 레플리카를 승격시킵니다. 레플리카는 읽기를 제공해 부하를 줄일 수도 있습니다.
단점은 시차입니다. 읽기 레플리카는 밀리초에서 초 단위로 뒤처질 수 있습니다. 사용자가 “방금 업데이트했으니 바로 보여야 한다”고 기대하면 이 차이가 문제 됩니다.
샤딩 환경에서는 보통 샤드 내부는 강한 일관성, 샤드 간은 더 약한 보장을 가지는 경우가 많습니다.
샤딩에서 “단일 진실 소스”는 보통: 특정 데이터 조각에 대해 쓰기의 권한이 한 곳(대부분 샤드의 리더)에 있다는 의미입니다. 그러나 전역적으로 ‘모든 것의 최신 상태’를 즉시 확인해줄 수 있는 한 머신은 없습니다. 여러 로컬 진실을 복제로 동기화해야 합니다.
서로 다른 샤드에 있는 데이터를 검증해야 하는 제약은 까다롭습니다:
이 선택들은 단순한 구현 디테일이 아니라 제품 관점에서 ‘정확함’이 무엇인지 정의합니다.
리밸런싱은 현실이 변할 때 샤딩된 DB를 사용 가능하게 유지하는 작업입니다. 데이터가 고르지 않게 성장하거나 샤드 키의 균형이 깨지거나 용량을 추가/폐기해야 할 때 필요합니다. 이런 상황은 한 샤드를 병목으로 만들 수 있습니다.
단일 DB와 달리 샤딩은 데이터의 위치를 라우팅 논리에 집어넣습니다. 데이터를 옮길 때는 바이트를 복사하는 것뿐 아니라 쿼리가 가야 할 곳을 바꾸는 일이 됩니다. 따라서 리밸런싱은 메타데이터와 클라이언트 문제이기도 합니다.
많은 팀이 중단 없는 워크플로를 목표로 합니다:
클라이언트가 라우팅 결정을 캐시하면 샤드 맵 변경은 큰 문제를 일으킬 수 있습니다. 좋은 시스템은 라우팅 메타데이터를 설정처럼 취급해 버전 관리하고 자주 갱신하며, 클라이언트가 이동된 키를 만났을 때(리다이렉트, 재시도, 프록시 등) 어떻게 동작할지 명확히 해야 합니다.
리밸런싱은 성능 저하(추가 쓰기, 캐시 교체, 백그라운드 복사 부하)를 일으킬 수 있습니다. 일부 범위만 먼저 이동하는 부분 이동이 흔하므로 관찰성과 롤백 계획(맵을 되돌리고 듀얼-라이트를 드레인하는 등)이 필요합니다.
샤딩은 작업이 분산된다는 가정에 기반하지만, 클러스터가 통계상 균형적이어도 실제 운영에서는 아주 불균형하게 행동할 수 있습니다.
핫스팟은 키 공간의 작은 부분이 대부분 트래픽을 차지할 때 발생합니다—예: 유명인 계정, 인기 상품, 한 테넌트의 무거운 배치 작업, 또는 “오늘”에 모든 쓰기가 몰리는 시간 기반 키. 이런 키들이 하나의 샤드에 매핑되면 그 샤드가 병목이 됩니다.
“스큐”는 한 가지가 아닙니다:
데이터와 트래픽의 스큐는 항상 일치하지 않습니다. 데이터가 적어도 요청이 많으면 핫해질 수 있습니다.
복잡한 추적 없이도 스큐를 포착할 수 있습니다. 샤드별 대시보드를 마련하세요:
한 샤드의 지연이 QPS와 함께 상승하는데 다른 샤드가 평온하면 핫스팟일 가능성이 큽니다.
해결책들은 대개 단순성 대 균형의 절충입니다:
샤딩(수평 파티셔닝)은 하나의 논리적 데이터셋을 여러 머신(“샤드”)에 분할해서 저장하는 방식으로, 각 샤드는 서로 다른 행을 보관합니다.
반면 복제(Replication)는 동일한 데이터를 여러 노드에 복사하여 가용성과 읽기 확장을 확보하는 것이 주목적입니다.
수직 확장(스케일 업)은 하나의 데이터베이스 서버를 더 좋은 CPU/RAM/디스크로 업그레이드하는 방식입니다. 운영적으로는 더 간단하지만 결국 한계에 부딪히거나 비용이 급증할 수 있습니다.
샤딩은 머신을 추가해 수평으로 확장하므로 용량을 늘리지만, 라우팅, 리밸런싱, 샤드 간 일관성 같은 복잡도를 함께 도입합니다.
팀이 샤딩을 도입하는 이유는 단일 노드가 반복적으로 병목이 될 때입니다. 예를 들면:
샤딩은 데이터와 트래픽을 여러 노드로 분산시켜 노드를 추가하면서 용량을 늘리게 해줍니다.
전형적인 샤딩 시스템은 다음을 포함합니다:
성능과 정합성은 이 구성요소들이 일관되게 동작하는지에 달려 있습니다.
샤드 키는 행을 어느 샤드에 둘지 결정하는 필드(또는 필드 조합)입니다. 이 결정은 요청이 단일 샤드로 라우팅되는지(빠른 경로) 아니면 여러 샤드로 팬아웃되는지(느린 경로)를 좌우합니다.
좋은 샤드 키는 보통 높은 카디널리티, 균등한 분포, 그리고 현재와 향후의 주요 접근 패턴과의 정렬을 갖습니다(예: tenant_id 또는 user_id).
일반적인 ‘나쁜’ 샤드 키:
이들은 흔히 핫스팟을 만들거나 루틴 쿼리를 스캐터-개더로 만들며 성능 문제를 유발합니다.
대표적인 샤딩 전략은 다음과 같습니다:
실무에서는 복합 키(예: tenant_id + user_id)나 서브샤딩으로 접근 패턴과 불균형을 동시에 다루기도 합니다.
샤딩된 DB에는 두 가지 쿼리 경로가 있습니다:
조인과 집계도 보통 각 샤드에서 부분 결과를 계산한 뒤 머지하는 두 단계 플랜이 필요해 비용이 커집니다.
단일 샤드로 라우팅되는 쓰기는 해당 샤드의 일반 트랜잭션 메커니즘을 사용해 빠르고 단순합니다.
그러나 두 개 이상의 샤드를 건드는 쓰기는 분산 트랜잭션 문제로 이어집니다. 2단계 커밋 같은 프로토콜은 왕복과 잠금, 타임아웃의 애매함을 도입합니다(조직은 적용 여부에 따라 복잡성을 감수해야 함).
교차 샤드 쓰기를 줄이는 패턴:
또한 재시도에 대비해 하게 설계(고정된 오퍼레이션 ID 사용)하는 것이 필수적입니다.
샤딩은 샤드 내부에서의 복제를 제거하지 않습니다. 각 샤드는 일반적으로 리더(프라이머리)와 복제본들을 가지며, 프라이머리가 실패하면 복제본으로 승격합니다.
일관성 모델 요약:
샤딩 환경에서는 보통 샤드 내부에서는 강한 일관성, 샤드 간에는 약한 보장을 갖는 경우가 많아 글로벌 제약(유일성, 외래키 등)이 어려워집니다.
글로벌 제약 대응 예시:
리밸런싱과 리샤딩은 시스템이 성장하거나 키 분포가 변할 때 필수적입니다. 문제는 데이터 위치를 옮기는 것이 곧 라우팅 메타데이터를 바꾸는 일이어서 온라인 상태에서의 이동이 복잡해진다는 점입니다.
일반적인 온라인 마이그레이션 패턴(복사 → 오버랩 → 컷오버):
클라이언트가 라우팅 결정을 캐시하면 샤드 맵 변경이 깨지는 이벤트가 될 수 있으니 메타데이터 버전 관리와 빈번한 갱신이 필요합니다.
샤딩은 작업이 고르게 분산된다는 가정에 기반하지만 실제로는 다음과 같은 불균형이 자주 발생합니다:
빠르게 감지하려면 샤드별 대시보드가 필요합니다:
해결책은 간단함과 균형 사이의 절충입니다: 트래픽을 나누도록 샤드 키 재설계, 버킷/솔트 적용, 캐싱, 레이트 리밋 및 핫 샤드 분할 등이 있습니다.
샤딩은 서버 수만 늘리는 것이 아니라 오류가 발생할 수 있는 지점도 늘립니다. 흔한 실패 모드:
디버깅은 요청을 샤드 전반에 걸쳐 추적할 수 있어야 합니다. 상관 ID(correlation ID)와 분산 추적을 사용해 어떤 샤드가 느린지/실패했는지 파악하고, 메트릭은 반드시 샤드별로 수집해야 합니다.
샤딩은 복잡도를 영구적으로 늘리므로, 가능하면 하나의 논리 DB를 유지하는 옵션을 우선 고려해야 합니다. 대안으로는:
샤딩 전 프로토타입 전략: 라우팅 경계, 아이덤포텐시, 마이그레이션 워크플로, 관찰성 등을 샌드박스에서 미리 실험해 운영 리스크를 줄이는 것이 좋습니다. 예로 같은 도구로 샤드-키 인지 API, 컷오버 행동 등을 안전한 환경에서 시도해볼 수 있습니다.
백업과 복구는 ‘데이터베이스 복구’가 아니라 여러 파트를 올바른 순서로 복원해 일관성을 확인하는 과정입니다. 메타데이터를 먼저 복원한 뒤 각 샤드를 복원하는 연습이 필요합니다.
샤딩이 적절한 경우는 데이터량이나 쓰기 처리량이 분명히 단일 노드 한계를 넘고, 핵심 쿼스의 90% 이상을 샤드 키로 라우팅할 수 있을 때입니다. 반대로 다수의 애드혹 쿼리, 잦은 다엔티티 트랜잭션, 글로벌 유일성 제약이 많거나 운영팀 역량이 부족하면 샤딩은 적합하지 않습니다.
간단한 체크리스트:
성장을 위한 설계(마이그레이션 경로, 식별자 선택, 싱글 노드 가정 피하기)를 미리 해두는 것이 중요합니다.