KoderKoder.ai
가격엔터프라이즈교육투자자용
로그인시작하기

제품

가격엔터프라이즈투자자용

리소스

문의하기지원교육블로그

법적 고지

개인정보 처리방침이용 약관보안허용 사용 정책악용 신고

소셜

LinkedInTwitter
Koder.ai
언어

© 2026 Koder.ai. All rights reserved.

홈›블로그›데이터베이스 샤딩의 작동 원리 — 그리고 왜 이해하기 어려운가
2025년 5월 07일·6분

데이터베이스 샤딩의 작동 원리 — 그리고 왜 이해하기 어려운가

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

데이터베이스 샤딩의 작동 원리 — 그리고 왜 이해하기 어려운가

샤딩이란(그리고 아닌 것)

샤딩(수평 파티셔닝이라고도 함)은 애플리케이션에겐 하나로 보이는 데이터베이스의 데이터를 여러 머신(샤드)으로 나누는 것을 의미합니다. 각 샤드는 행의 일부만 보관하지만, 함께 모이면 전체 데이터셋을 구성합니다.

하나의 논리 테이블, 여러 물리적 장소

도움이 되는 사고 모델은 논리적 구조와 물리적 배치의 차이입니다.

  • 논리적: 여전히 하나의 “Users” 테이블이 있습니다(컬럼과 의미는 동일).
  • 물리적: 그 테이블의 행들은 여러 곳에 분산 저장됩니다—예: ID 1–1,000,000 사용자는 샤드 A에, 다음 백만은 샤드 B에.

애플리케이션 관점에서 쿼리는 하나의 테이블처럼 실행되길 원합니다. 내부적으로는 시스템이 어떤 샤드에 접속할지 결정해야 합니다.

복제가 아니며, ‘큰 서버로 교체’와도 다름

샤딩은 복제와 다릅니다. 복제는 동일한 데이터를 여러 노드에 복사해 주로 가용성과 읽기 확장을 담당합니다. 샤딩은 데이터를 분할해 각 노드가 다른 레코드를 보관합니다.

또한 수직 확장(더 큰 기계로 이전해 CPU/RAM/디스크를 늘리는 것)과도 다릅니다. 수직 확장은 간단할 수 있지만 한계와 비용 문제가 있습니다.

샤딩이 마법처럼 모든 문제를 해결하지는 않는다

샤딩은 용량을 늘리지만 데이터베이스의 모든 쿼리를 자동으로 빠르게 만들거나 운영을 단순화하지는 않습니다.

  • 조인은 관련 행들이 다른 샤드에 있으면 비용이 커집니다.
  • 샤드 간 트랜잭션은 더 어렵고, 전체적 원자성 보장이 힘듭니다.
  • 운영 복잡성이 늘어납니다: 라우팅, 리밸런싱, 디버깅, 실패 처리 등이 시스템의 일부가 됩니다.

따라서 샤딩은 스토리지와 처리량을 수평으로 확장하는 수단으로 이해해야지, 모든 면에서 무료 업그레이드는 아닙니다.

팀이 샤딩을 선택하는 이유: 해결하려는 문제들

샤딩은 보통 첫 선택이 아닙니다. 성공적으로 성장한 시스템이 물리적 한계에 부딪히거나 운영상의 고통이 잦아질 때 도입됩니다. 동기는 ‘샤딩을 하고 싶다’가 아니라 ‘한 DB가 단일 장애 지점이자 비용 중심이 되는 걸 피하고 싶다’입니다.

샤딩을 촉발하는 문제점들

단일 DB 노드는 여러 방식으로 한계에 이를 수 있습니다:

  • 저장 한계: 테이블과 인덱스가 커져 디스크 여유가 줄고, 백업이 느려지며 유지보수가 위험해짐.
  • 쓰기 처리량 한계: CPU, WAL/redo, 락 경합 등이 초당 처리 가능한 쓰기를 제한함.
  • 읽기 처리량 한계: 캐시와 레플리카가 있어도 일부 워크로드는 프라이머리를 압도함.
  • 노이즈 이웃: 특정 테넌트나 워크로드 패턴이 리소스를 독차지해 다른 사용자 성능을 악화시킴.

이 문제가 반복되면 흔히 원인은 ‘한 머신이 너무 많은 책임을 지고 있다’는 것입니다.

목표: 수평 확장, 분리(isolate), 비용 제어

데이터베이스 샤딩은 데이터를 여러 노드로 나눠 머신을 추가하면서 용량을 키우게 해줍니다. 잘 하면 워크로드를 격리해(한 테넌트의 스파이크가 다른 이의 지연을 망치지 않게) 비용을 통제할 수 있습니다.

한계에 가까워지고 있음을 알려주는 초기 신호

반복되는 패턴은 피크 시 p95/p99 지연 상승, 복제 지연 증가, 백업/복구 시간이 허용 창을 초과, 사소한 스키마 변경이 큰 이벤트가 되는 경우 등입니다.

샤딩은 보통 최후의 수단인 이유

팀은 보통 샤딩 전 더 간단한 옵션을 다 사용해봅니다: 인덱싱과 쿼리 개선, 캐싱, 리드 레플리카, 단일 DB 내 파티셔닝, 오래된 데이터 아카이빙, 하드웨어 업그레이드 등. 샤딩은 확장을 해결하지만 조정, 운영 복잡성, 새로운 실패 모드를 함께 가져오므로 결정 기준이 높아야 합니다.

핵심 구성요소: 샤드, 라우터, 메타데이터

샤딩된 데이터베이스는 하나의 엔진이 아니라 협력하는 작은 시스템입니다. 샤딩이 “이해하기 어렵다” 느껴지는 이유는 성능과 정합성이 DB 엔진뿐 아니라 이 구성요소들의 상호작용에 달려 있기 때문입니다.

샤드: 독립된 파티션(자체 인덱스 포함)

샤드는 보통 자체 서버나 클러스터에 저장되는 데이터의 부분집합입니다. 각 샤드는 일반적으로:

  • 스토리지(데이터 파일)
  • 인덱스(샤드 내 쿼리를 빠르게 하기 위함)
  • 로컬 한계(CPU, 메모리, 디스크, 연결 수)

애플리케이션 관점에서 샤딩된 구성은 하나의 논리 DB처럼 보이려 합니다. 그러나 단일 노드에서는 “인덱스 조회 하나”로 끝나던 쿼리가 샤딩에서는 “적절한 샤드를 찾고, 그 다음 조회”로 바뀝니다.

라우터/코디네이터: 요청이 올바른 샤드에 닿는 방법

라우터(코디네이터, 쿼리 라우터, 프록시라고도 함)는 교통정리 역할을 합니다. 실무적 질문에 답합니다: 이 요청은 어떤 샤드가 처리해야 하나?

두 가지 일반적 패턴이 있습니다:

  1. 클라이언트 사이드 라우팅: 애플리케이션 라이브러리가 샤드 맵을 알고 직접 적절한 샤드에 연결합니다.
  2. 프록시 라우팅: 앱은 라우터 서비스에 연결하고 라우터가 요청을 전달합니다.

라우터는 앱 복잡도를 줄여주지만, 잘못 설계되면 병목이나 새로운 장애 지점이 될 수 있습니다.

메타데이터/설정 서비스: 샤드 맵, 소유권, 상태

샤딩은 다음과 같은 메타데이터에 의존합니다:

  • 샤드 맵: 어떤 샤드가 어떤 범위/버킷/ID를 소유하는가
  • 소유권: 특히 마이그레이션 중 소유권이 일시적으로 겹칠 때 중요
  • 건강 및 멤버십: 어떤 노드가 업인지, 프라이머리/리플리카 역할, 드레인 상태 등

이 정보는 설정 서비스(혹은 작은 컨트롤 플레인 DB)에 저장됩니다. 메타데이터가 오래되거나 불일치하면, 샤드들이 완전히 건강하더라도 라우터가 잘못된 곳으로 트래픽을 보낼 수 있습니다.

백그라운드 작업: 밸런싱, 마이그레이션, 백업

샤딩은 시간이 지나도 시스템을 유지하게 하는 백그라운드 프로세스에 의존합니다:

  • 한 샤드가 다른 샤드보다 빠르게 성장하면 데이터를 리밸런싱
  • 샤드 간 소유권을 이동하는 마이그레이션
  • 여러 샤드에 걸친 백업/복구 절차(복구 목표와 맞아야 함)

이 작업들은 초기에 무시하기 쉽지만, 운영 중 시스템의 형태를 바꾸기 때문에 많은 프로덕션 사고의 진원지입니다.

샤드 키 선택: 첫 번째 큰 트레이드오프

샤드 키는 행/문서를 어느 샤드에 저장할지 결정하는 필드입니다. 이 한 가지 선택이 나중에 성능, 비용, 기능 편의성에 큰 영향을 줍니다—요청이 단일 샤드로 라우팅되는지 아니면 많은 샤드로 퍼지는지를 결정하기 때문입니다.

좋은 샤드 키의 특징

좋은 키는 보통 다음을 가집니다:

  • 높은 카디널리티: 가능한 값이 많음(예: user_id는 country보다 낫다)
  • 균등 분포: 값이 샤드들에 읽기/쓰기를 고르게 분산시킴
  • 안정적인 접근 패턴: 현재와 향후 쿼리 패턴과 일치

예: 멀티테넌트 앱에서 tenant_id로 샤딩하면 한 테넌트의 대부분 읽기/쓰기가 한 샤드에 머물러 로컬 처리가 가능하고, 테넌트가 많으면 부하를 분산시킬 수 있습니다.

나쁜 샤드 키의 문제점

일부 키는 문제를 보장합니다:

  • 시간 기반 단조 키(타임스탬프, 오토인크리먼트 ID): 새 데이터가 항상 최신 샤드로 몰려 쓰기 핫스팟 발생
  • 저카디널리티 필드(status, plan_tier, country): 구별되는 값이 적어 일부 샤드에 부하 집중
  • 변경 가능한 식별자(이메일, 변경 가능한 사용자명): 키가 바뀌면 데이터를 샤드 간 이동해야 해 비용·위험 증가

심지어 저카디널리티 키가 필터링에는 편리해 보여도 관련 행들이 모든 샤드에 흩어져 스캐터-개더 쿼리가 되어버립니다.

실제 트레이드오프: 쿼리 편의성 vs 분포 품질

부하 균형에 가장 좋은 샤드 키가 항상 제품 쿼리 관점에서 최선은 아닙니다.

  • 주요 접근 패턴(user_id)에 맞춘 키를 고르면 일부 글로벌 쿼리(관리자 리포팅 등)가 느려지거나 별도 파이프라인이 필요할 수 있습니다.
  • 리포팅 중심 키(region)를 고르면 핫스팟과 불균등 용량 문제가 생길 수 있습니다.

대부분 팀은 가장 빈번하고 지연에 민감한 작업에 맞춰 샤드 키를 최적화하고, 나머지는 인덱스, 디노멀라이제이션, 레플리카 또는 전용 분석 테이블로 처리합니다.

일반적인 샤딩 전략(레인지, 해시, 디렉터리)

멀티테넌트 샤드 모델링
작은 멀티테넌트 앱을 만들어 tenant_id 샤딩이 쿼리에 어떤 영향을 주는지 확인하세요.
무료로 시작

최고의 방법은 없습니다. 어느 전략을 택하느냐에 따라 라우팅의 용이성, 데이터 분포의 균일성, 그리고 어떤 접근 패턴이 문제를 일으키는지가 결정됩니다.

레인지 샤딩

레인지 샤딩은 각 샤드가 키 공간의 연속 구간을 소유합니다—예:

  • 샤드 A: customer_id 1–1,000,000
  • 샤드 B: customer_id 1,000,001–2,000,000 \라우팅은 단순합니다: 키를 보고 샤드를 고르면 됩니다. \단점은 핫스팟입니다. 새 사용자가 항상 증가하는 ID를 받는다면 마지막 샤드에 쓰기가 몰려 병목이 됩니다. 또한 불균형 성장에 민감합니다. 장점은 범위 쿼리(예: 10월 1일~10월 31일의 모든 주문)가 효율적일 수 있다는 점입니다.

해시 샤딩

해시 샤딩은 샤드 키에 해시 함수를 적용해 샤드를 선택합니다. 일반적으로 데이터가 더 고르게 분포되어 ‘최신 샤드로 몰림’ 문제를 피할 수 있습니다.

트레이드오프: 레인지 쿼리가 비싸집니다—예: 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가 쉽게 참조 무결성을 강제할 수 없습니다.
  • 카운터(글로벌 합계, 순차 ID): 단순한 접근은 병목을 만듭니다. 해결책은 샤드별 범위 할당, 배치 처리, 또는 근사값 허용 등입니다.

이 선택들은 단순한 구현 디테일이 아니라 제품 관점에서 ‘정확함’이 무엇인지 정의합니다.

무중단 리밸런싱과 리샤딩

코드 가져오기
설계가 만족스러워지면 소스 코드를 내보내 전체 소유권을 유지하세요.
코드 내보내기

리밸런싱은 현실이 변할 때 샤딩된 DB를 사용 가능하게 유지하는 작업입니다. 데이터가 고르지 않게 성장하거나 샤드 키의 균형이 깨지거나 용량을 추가/폐기해야 할 때 필요합니다. 이런 상황은 한 샤드를 병목으로 만들 수 있습니다.

어려운 이유

단일 DB와 달리 샤딩은 데이터의 위치를 라우팅 논리에 집어넣습니다. 데이터를 옮길 때는 바이트를 복사하는 것뿐 아니라 쿼리가 가야 할 곳을 바꾸는 일이 됩니다. 따라서 리밸런싱은 메타데이터와 클라이언트 문제이기도 합니다.

온라인 마이그레이션 패턴(복사 → 오버랩 → 컷오버)

많은 팀이 중단 없는 워크플로를 목표로 합니다:

  1. 복사: 소스 샤드에서 대상 샤드로 백필 수행(서비스 중단 없이)
  2. 듀얼-라이트(때로 듀얼-리드): 전환 기간 동안 새/옛 위치에 모두 쓰기. 읽기는 둘 다 참조하거나 “새 데이터 우선” 규칙 사용
  3. 컷오버: 샤드 맵을 갱신해 라우터/클라이언트가 새 위치로 트래픽을 보냄
  4. 정리: 듀얼-라이트 중단, 옛 복사본 제거, 공간 회수

샤드 맵과 클라이언트 동작

클라이언트가 라우팅 결정을 캐시하면 샤드 맵 변경은 큰 문제를 일으킬 수 있습니다. 좋은 시스템은 라우팅 메타데이터를 설정처럼 취급해 버전 관리하고 자주 갱신하며, 클라이언트가 이동된 키를 만났을 때(리다이렉트, 재시도, 프록시 등) 어떻게 동작할지 명확히 해야 합니다.

운영 리스크 계획

리밸런싱은 성능 저하(추가 쓰기, 캐시 교체, 백그라운드 복사 부하)를 일으킬 수 있습니다. 일부 범위만 먼저 이동하는 부분 이동이 흔하므로 관찰성과 롤백 계획(맵을 되돌리고 듀얼-라이트를 드레인하는 등)이 필요합니다.

핫스팟과 스큐: “균등 분할”이 깨질 때

샤딩은 작업이 분산된다는 가정에 기반하지만, 클러스터가 통계상 균형적이어도 실제 운영에서는 아주 불균형하게 행동할 수 있습니다.

핫 파티션(핫 키)

핫스팟은 키 공간의 작은 부분이 대부분 트래픽을 차지할 때 발생합니다—예: 유명인 계정, 인기 상품, 한 테넌트의 무거운 배치 작업, 또는 “오늘”에 모든 쓰기가 몰리는 시간 기반 키. 이런 키들이 하나의 샤드에 매핑되면 그 샤드가 병목이 됩니다.

스큐: 데이터 크기 vs 트래픽

“스큐”는 한 가지가 아닙니다:

  • 데이터 스큐: 한 샤드가 더 많은 바이트/행을 가짐(저장 압력, 느린 백업, 스캔 지연)
  • 트래픽 스큐: 한 샤드가 더 많은 QPS나 무거운 쿼리를 처리함(CPU 포화, 큐잉, 지연 상승)

데이터와 트래픽의 스큐는 항상 일치하지 않습니다. 데이터가 적어도 요청이 많으면 핫해질 수 있습니다.

빠르게 감지하는 방법

복잡한 추적 없이도 스큐를 포착할 수 있습니다. 샤드별 대시보드를 마련하세요:

  • 샤드별 p95 지연(하나의 샤드가 치우쳐 있으면 경고)
  • 샤드별 QPS(읽기/쓰기)
  • 샤드별 저장 공간/테이블 크기

한 샤드의 지연이 QPS와 함께 상승하는데 다른 샤드가 평온하면 핫스팟일 가능성이 큽니다.

완화책

해결책들은 대개 단순성 대 균형의 절충입니다:

  • 트래픽을 분산하는 샤드 키 선택(단순 레코드 수 분산에만 초점 맞추지 않음)
  • 핫 키에 대해 버킷화/솔팅 적용(하나의 논리 키를 여러 물리 버킷으로 나눔)
  • 읽기 중심 핫 아이템에 캐싱 적용
  • 클러스터를 보호하기 위한 레이트 리밋 또는 테넌트별 쿼터 적용
  • 한 샤드로는 해결 불가능하면 핫 샤드 분할 또는 핫 레인지 이동

자주 묻는 질문

데이터베이스 샤딩이란 무엇이며 복제와 어떻게 다른가요?

샤딩(수평 파티셔닝)은 하나의 논리적 데이터셋을 여러 머신(“샤드”)에 분할해서 저장하는 방식으로, 각 샤드는 서로 다른 행을 보관합니다.

반면 복제(Replication)는 동일한 데이터를 여러 노드에 복사하여 가용성과 읽기 확장을 확보하는 것이 주목적입니다.

샤딩 대신 서버를 더 키우는 것이 왜 안 되나요?

수직 확장(스케일 업)은 하나의 데이터베이스 서버를 더 좋은 CPU/RAM/디스크로 업그레이드하는 방식입니다. 운영적으로는 더 간단하지만 결국 한계에 부딪히거나 비용이 급증할 수 있습니다.

샤딩은 머신을 추가해 수평으로 확장하므로 용량을 늘리지만, 라우팅, 리밸런싱, 샤드 간 일관성 같은 복잡도를 함께 도입합니다.

샤딩이 실제로 어떤 문제를 해결하나요?

팀이 샤딩을 도입하는 이유는 단일 노드가 반복적으로 병목이 될 때입니다. 예를 들면:

  • 디스크와 인덱스가 커져 백업/운영이 느려짐
  • CPU/WAL/락 경합으로 쓰기 처리량 한계
  • 읽기 부하가 프라이머리/레플리카를 압도함
  • 특정 테넌트나 워크로드가 리소스를 독점(‘노이즈 이웃’)

샤딩은 데이터와 트래픽을 여러 노드로 분산시켜 노드를 추가하면서 용량을 늘리게 해줍니다.

샤딩된 데이터베이스 시스템의 핵심 구성 요소는 무엇인가요?

전형적인 샤딩 시스템은 다음을 포함합니다:

  • 샤드: 독립된 파티션(자체 스토리지와 인덱스 보유)
  • 라우터/코디네이터: 어떤 샤드에 요청을 보낼지 결정
  • 메타데이터/설정 서비스: 샤드 맵, 소유권, 상태, 멤버십 정보
  • 백그라운드 작업: 리밸런싱, 마이그레이션, 백업/복구 워크플로우

성능과 정합성은 이 구성요소들이 일관되게 동작하는지에 달려 있습니다.

샤드 키란 무엇이며 왜 그렇게 중요한가요?

샤드 키는 행을 어느 샤드에 둘지 결정하는 필드(또는 필드 조합)입니다. 이 결정은 요청이 단일 샤드로 라우팅되는지(빠른 경로) 아니면 여러 샤드로 팬아웃되는지(느린 경로)를 좌우합니다.

좋은 샤드 키는 보통 높은 카디널리티, 균등한 분포, 그리고 현재와 향후의 주요 접근 패턴과의 정렬을 갖습니다(예: tenant_id 또는 user_id).

샤드 키가 ‘나쁘다’는 것은 무엇을 의미하며 어떤 문제가 발생하나요?

일반적인 ‘나쁜’ 샤드 키:

  • 단조적/시간 기반 키(타임스탬프, 오토인크리먼트 ID): 최신 샤드에 쓰기가 몰려 핫스팟 발생
  • 저카디널리티 필드(status, plan_tier, country): 값이 적어 특정 샤드에 부하 집중
  • 변경 가능한 식별자(이메일, 변경 가능한 사용자명): 키가 바뀌면 데이터 이동이 위험하고 비용이 큼

이들은 흔히 핫스팟을 만들거나 루틴 쿼리를 스캐터-개더로 만들며 성능 문제를 유발합니다.

레인지, 해시, 디렉터리 샤딩은 무엇이며 언제 각각을 써야 하나요?

대표적인 샤딩 전략은 다음과 같습니다:

  • 레인지 샤딩: 키 공간의 연속 구간을 샤드가 소유(라우팅은 단순). 단, 모노토닉한 ID 생성 시 최신 샤드에 쓰기가 몰려 핫스팟 위험.
  • 해시 샤딩: 키를 해시해 샤드를 선택하므로 데이터가 고르게 분포되는 경향. 단점은 레인지 쿼리가 여러 샤드를 건드리게 되는 것.
  • 디렉터리(룩업) 샤딩: 키→샤드 매핑을 명시적으로 저장해 유연한 배치와 개별 이동 가능. 대신 디렉터리 서비스가 추가 의존성이 됨.

실무에서는 복합 키(예: tenant_id + user_id)나 서브샤딩으로 접근 패턴과 불균형을 동시에 다루기도 합니다.

샤딩 후 어떤 쿼리가 느려지나요(스캐터-개더란 무엇인가요)?

샤딩된 DB에는 두 가지 쿼리 경로가 있습니다:

  • 단일 샤드 쿼리(빠른 경로): 요청에 샤드 키가 포함되어 정확히 한 샤드로 라우팅되는 경우. 네트워크 왕복과 조정이 적어 빠릅니다.
  • 스캐터-개더(팬아웃): 샤드 키가 없거나 필터가 비샤드키 필드인 경우 많은(또는 모든) 샤드에 요청을 보내 각 샤드 결과를 병합합니다. 이때 한 샤드의 느린 응답이 전체 지연을 좌우합니다.

조인과 집계도 보통 각 샤드에서 부분 결과를 계산한 뒤 머지하는 두 단계 플랜이 필요해 비용이 커집니다.

샤드 간의 쓰기와 트랜잭션은 어떻게 작동하나요?

단일 샤드로 라우팅되는 쓰기는 해당 샤드의 일반 트랜잭션 메커니즘을 사용해 빠르고 단순합니다.

그러나 두 개 이상의 샤드를 건드는 쓰기는 분산 트랜잭션 문제로 이어집니다. 2단계 커밋 같은 프로토콜은 왕복과 잠금, 타임아웃의 애매함을 도입합니다(조직은 적용 여부에 따라 복잡성을 감수해야 함).

교차 샤드 쓰기를 줄이는 패턴:

  • 데이터 로컬리티: 관련 레코드를 같은 샤드에 둠
  • 요청 소유권: 특정 샤드가 작업을 소유하도록 설계
  • 디노멀라이제이션: 작은 데이터를 복제해 팬아웃 업데이트를 피함

또한 재시도에 대비해 하게 설계(고정된 오퍼레이션 ID 사용)하는 것이 필수적입니다.

일관성과 복제는 샤딩에서 어떻게 관리되나요?

샤딩은 샤드 내부에서의 복제를 제거하지 않습니다. 각 샤드는 일반적으로 리더(프라이머리)와 복제본들을 가지며, 프라이머리가 실패하면 복제본으로 승격합니다.

일관성 모델 요약:

  • 강한 일관성: 쓰기가 성공하면 이후의 읽기는 그 결과를 반영함(보통 리더에서 읽거나 복제 확인 대기 필요).
  • 최종 일관성: 시스템은 수렴하지만 일시적으로 오래된 데이터를 읽을 수 있음.

샤딩 환경에서는 보통 샤드 내부에서는 강한 일관성, 샤드 간에는 약한 보장을 갖는 경우가 많아 글로벌 제약(유일성, 외래키 등)이 어려워집니다.

글로벌 제약 대응 예시:

무중단으로 리밸런싱/리샤딩하려면 어떻게 하나요?

리밸런싱과 리샤딩은 시스템이 성장하거나 키 분포가 변할 때 필수적입니다. 문제는 데이터 위치를 옮기는 것이 곧 라우팅 메타데이터를 바꾸는 일이어서 온라인 상태에서의 이동이 복잡해진다는 점입니다.

일반적인 온라인 마이그레이션 패턴(복사 → 오버랩 → 컷오버):

  1. 복사: 소스 샤드에서 대상 샤드로 백필(backfill)
  2. 듀얼-라이트(때로는 듀얼-리드): 전환 기간 동안 새/옛 위치에 모두 쓰기
  3. 컷오버: 샤드 맵을 갱신해 트래픽을 새 위치로 보냄
  4. 정리: 듀얼-라이트 중단, 옛 데이터 제거

클라이언트가 라우팅 결정을 캐시하면 샤드 맵 변경이 깨지는 이벤트가 될 수 있으니 메타데이터 버전 관리와 빈번한 갱신이 필요합니다.

핫스팟과 스큐는 무엇이며 어떻게 대응하나요?

샤딩은 작업이 고르게 분산된다는 가정에 기반하지만 실제로는 다음과 같은 불균형이 자주 발생합니다:

  • 핫 파티션(핫 키): 인기 있는 계정, 인기 상품, 특정 시간대의 쓰기가 한 키에 집중되어 해당 샤드가 병목이 됨
  • 스큐(편향): 데이터 크기와 트래픽이 일치하지 않을 수 있음(작은 데이터지만 요청이 많아 핫한 샤드 등)

빠르게 감지하려면 샤드별 대시보드가 필요합니다:

  • 샤드별 p95 지연
  • 샤드별 QPS(읽기/쓰기)
  • 샤드별 저장 공간 사용량

해결책은 간단함과 균형 사이의 절충입니다: 트래픽을 나누도록 샤드 키 재설계, 버킷/솔트 적용, 캐싱, 레이트 리밋 및 핫 샤드 분할 등이 있습니다.

샤딩 시스템에서 흔한 실패 모드와 디버깅 방식은 무엇인가요?

샤딩은 서버 수만 늘리는 것이 아니라 오류가 발생할 수 있는 지점도 늘립니다. 흔한 실패 모드:

  • 샤드 비가용: 하나의 샤드가 죽거나 디스크가 꽉 차면 일부 고객만 영향 받음
  • 라우터의 잘못된 라우팅: 설정 변경이나 배포 오류로 트래픽이 잘못 전달될 수 있음
  • 메타데이터 불일치: 마이그레이션 중 서로 다른 컴포넌트가 다르게 라우팅하면 일관성 문제가 발생
  • 부분 네트워크 문제: 라우터와 일부 샤드 간 타임아웃은 재시도가 부하를 증폭시킴

디버깅은 요청을 샤드 전반에 걸쳐 추적할 수 있어야 합니다. 상관 ID(correlation ID)와 분산 추적을 사용해 어떤 샤드가 느린지/실패했는지 파악하고, 메트릭은 반드시 샤드별로 수집해야 합니다.

언제 샤딩을 피해야 하고, 실용적인 대안은 무엇인가요?

샤딩은 복잡도를 영구적으로 늘리므로, 가능하면 하나의 논리 DB를 유지하는 옵션을 우선 고려해야 합니다. 대안으로는:

  • 인덱스와 쿼리 튜닝: 빠른 경로를 먼저 고침(누락된 인덱스, N+1 등)
  • 캐싱: 읽기 중심의 안정적인 응답을 캐시로 처리
  • 리드 레플리카: 쓰기 경로 변경 없이 읽기를 분산(레플리카 지연 허용 시)
  • 단일 노드 내 테이블 파티셔닝: 노드 하나에서 파티셔닝으로 유지보수와 쿼리 성능 개선
  • 오래된 데이터 아카이빙

샤딩 전 프로토타입 전략: 라우팅 경계, 아이덤포텐시, 마이그레이션 워크플로, 관찰성 등을 샌드박스에서 미리 실험해 운영 리스크를 줄이는 것이 좋습니다. 예로 같은 도구로 샤드-키 인지 API, 컷오버 행동 등을 안전한 환경에서 시도해볼 수 있습니다.

목차
샤딩이란(그리고 아닌 것)팀이 샤딩을 선택하는 이유: 해결하려는 문제들핵심 구성요소: 샤드, 라우터, 메타데이터샤드 키 선택: 첫 번째 큰 트레이드오프일반적인 샤딩 전략(레인지, 해시, 디렉터리)쿼리가 어떻게 동작하는가: 라우팅 대 스캐터-개더쓰기와 샤드 간 트랜잭션일관성과 복제: 데이터를 올바르게 유지하기무중단 리밸런싱과 리샤딩핫스팟과 스큐: “균등 분할”이 깨질 때자주 묻는 질문
공유
Koder.ai
Koder로 나만의 앱을 만들어 보세요 지금!

Koder의 힘을 이해하는 가장 좋은 방법은 직접 체험하는 것입니다.

무료로 시작데모 예약
아이덤포텐트(idempotent)
  • 유일성(예: 사용자명): 중앙 색인, 전용 제약 샤드, 또는 애플리케이션 레벨 예약 워크플로 필요
  • 외래키: 부모/자식이 다른 샤드에 있으면 DB가 자동으로 강제하기 어려움
  • 카운터/순차 ID: 병목을 만들 수 있어 샤드별 범위 할당, 배치, 근사값 수용 등의 기법을 씀
  • 백업과 복구는 ‘데이터베이스 복구’가 아니라 여러 파트를 올바른 순서로 복원해 일관성을 확인하는 과정입니다. 메타데이터를 먼저 복원한 뒤 각 샤드를 복원하는 연습이 필요합니다.

    Koder.ai

    샤딩이 적절한 경우는 데이터량이나 쓰기 처리량이 분명히 단일 노드 한계를 넘고, 핵심 쿼스의 90% 이상을 샤드 키로 라우팅할 수 있을 때입니다. 반대로 다수의 애드혹 쿼리, 잦은 다엔티티 트랜잭션, 글로벌 유일성 제약이 많거나 운영팀 역량이 부족하면 샤딩은 적합하지 않습니다.

    간단한 체크리스트:

    • 병목이 CPU, I/O, 메모리, 락 중 무엇인지, 샤딩 없이 해결 가능한가?
    • 핵심 쿼스의 90% 이상을 샤드 키로 라우팅할 수 있는가?
    • 누가 샤드 맵, 온콜 룩북, 샤드 간 트랜잭션 동작을 책임지는가?
    • SLO: 일부 샤드가 다운될 때의 부분 저하와 꼬리 지연을 용인할 수 있는가?

    성장을 위한 설계(마이그레이션 경로, 식별자 선택, 싱글 노드 가정 피하기)를 미리 해두는 것이 중요합니다.