파티셔닝과 샤딩 차이: 파티션 프루닝과 샤드 키 설계


1장. 데이터를 나눴는데 왜 조회가 여전히 느릴까#

쇼핑몰 주문 데이터가 몇 년 동안 쌓였습니다.

처음에는 하나의 주문 테이블로도 문제가 없었습니다.

하지만 데이터가 수억 건에 가까워지면서 월별 매출 보고서와 오래된 주문 관리가 점점 부담스러워졌습니다.

그래서 주문을 월별로 나누었습니다.

2026년 1월 주문

2026년 2월 주문

2026년 3월 주문

...

월별 보고서는 훨씬 관리하기 쉬워졌습니다.

그런데 고객센터에서 이런 요청이 들어옵니다.

고객 M001의 지난 5년 주문을 모두 보여 주세요.

M001의 주문은 여러 달에 걸쳐 있습니다.

2025-02
2025-07
2026-01
2026-06
...

월별로 데이터를 나눴지만 고객 한 명의 이력을 찾으려면 여전히 여러 구간을 확인해야 합니다.

이번에는 고객ID 기준으로 데이터를 여러 서버에 나눠 저장했다고 해보겠습니다.

M001 주문은 한 서버에 모이기 쉬워졌습니다.

하지만 재무팀이 다음 요청을 합니다.

2026년 9월 전체 고객의 매출을 집계해 주세요.

이번에는 여러 서버의 데이터를 모아야 합니다.

즉 데이터를 나누면 모든 조회가 빨라지는 것이 아닙니다.

어떤 기준으로 나누느냐에 따라 빨라지는 질문과 어려워지는 질문이 달라집니다.

파티셔닝과 샤딩을 이해할 때 가장 중요한 출발점입니다.


2장. 파티셔닝과 샤딩은 모두 데이터를 나누지만 목적이 다르다#

두 방식 모두 큰 데이터를 여러 조각으로 나눕니다.

하지만 관리 범위가 다릅니다.

파티셔닝은 하나의 논리적인 데이터 집합을 일정한 규칙으로 여러 부분으로 나누어 관리하는 방식입니다.

샤딩은 데이터를 여러 데이터베이스 인스턴스나 저장 노드에 나누어 배치하는 방식입니다.

개념적으로 비교하면 다음과 같습니다.

flowchart LR
    A["논리적 주문 테이블"] --> P1["1월 파티션"]
    A --> P2["2월 파티션"]
    A --> P3["3월 파티션"]

파티셔닝에서는 사용자가 하나의 논리 테이블을 조회하더라도 DBMS 내부에서는 필요한 파티션만 접근할 수 있습니다.

샤딩은 다음과 비슷합니다.

flowchart LR
    Q["애플리케이션 요청"] --> R["샤드 라우터"]
    R --> S1["Shard 1"]
    R --> S2["Shard 2"]
    R --> S3["Shard 3"]

샤딩에서는 어느 노드가 데이터를 가지고 있는지 찾아 요청을 전달해야 합니다.


3장. 파티셔닝의 핵심은 데이터를 관리할 경계를 만드는 것이다#

주문 데이터를 월별로 나눈다고 하겠습니다.

주문ID 고객ID 주문일 금액
O1 M1 2026-01-15 20,000
O2 M2 2026-01-20 30,000
O3 M1 2026-02-02 15,000
O4 M3 2026-02-18 12,000

주문일을 기준으로 나누면:

2026-01 파티션
→ O1
→ O2

2026-02 파티션
→ O3
→ O4

가 됩니다.

논리적으로는 여전히 하나의 주문 테이블입니다.

하지만 물리적으로는 여러 구간으로 관리할 수 있습니다.

이렇게 하면 특정 월만 조회하거나 오래된 월 단위 데이터를 관리하기 쉬워질 수 있습니다.


4장. 범위 파티셔닝은 날짜 데이터에서 자주 사용된다#

범위 파티셔닝은 특정 값의 범위를 기준으로 데이터를 나눕니다.

대표적인 예가 날짜입니다.

2026-01-01 이상
2026-02-01 미만
→ 1월 파티션

2026-02-01 이상
2026-03-01 미만
→ 2월 파티션

이를 Mermaid로 단순화하면 다음과 같습니다.

flowchart TD
    O["ORDERS"] --> J["2026-01"]
    O --> F["2026-02"]
    O --> M["2026-03"]

이 방식은 다음 업무와 잘 맞을 수 있습니다.

지난달 주문 조회

월별 매출 집계

오래된 주문 보관

월 단위 데이터 삭제

5장. 날짜 범위는 반개구간으로 표현하는 것이 안전하다#

2026년 9월 주문을 찾는다고 하겠습니다.

다음처럼 작성할 수 있습니다.

WHERE order_date >= DATE '2026-09-01'
  AND order_date < DATE '2026-10-01'

이 방식은 시작은 포함하고 끝은 제외합니다.

2026-09-01
≤ 주문일
< 2026-10-01

시간까지 포함된 컬럼에서도 경계를 명확하게 표현하기 좋습니다.

다음처럼 월 마지막 날의 특정 시각을 직접 계산하는 것보다 오류 가능성을 줄일 수 있습니다.

2026-09-30 23:59:59.999...

파티션 경계와 실제 조회 조건이 같은 방식으로 정의되어 있으면 파티션 프루닝에도 유리합니다.


6장. 파티션 프루닝은 읽지 않아도 되는 파티션을 제외하는 것이다#

월별 파티션이 다음과 같다고 하겠습니다.

1월
2월
3월
4월

SQL이 다음과 같습니다.

SELECT order_id, amount
FROM orders
WHERE order_date >= DATE '2026-02-01'
  AND order_date < DATE '2026-03-01';

DBMS가 조건을 보고 2월 파티션만 필요하다고 판단할 수 있습니다.

flowchart LR
    Q["2월 주문 조회"] --> F["2월 파티션"]
    J["1월 파티션"] -. 제외 .-> Q
    M["3월 파티션"] -. 제외 .-> Q
    A["4월 파티션"] -. 제외 .-> Q

이렇게 불필요한 파티션을 조회 대상에서 제거하는 것이 파티션 프루닝입니다.


7장. 결과가 적다고 프루닝이 된 것은 아니다#

다음 SQL이 한 행만 반환했다고 하겠습니다.

SELECT *
FROM orders
WHERE customer_id = 'M001';

결과:

1행

그렇다고 한 파티션만 읽었다는 뜻은 아닙니다.

주문 테이블이 주문일 기준으로 파티션되어 있는데 조건에는 주문일이 없습니다.

그러면 M001이 어느 달에 주문했는지 알기 위해 여러 파티션을 확인해야 할 수 있습니다.

즉 다음 두 개는 완전히 다른 개념입니다.

결과 행 수가 적다

접근한 파티션 수가 적다

프루닝 여부는 실행 계획에서 실제 접근 파티션을 확인해야 합니다.


8장. 파티션 키와 조회 조건이 맞아야 효과가 커진다#

주문일로 파티션했다면 다음 조회에는 유리할 수 있습니다.

WHERE order_date >= ...
  AND order_date < ...

하지만 다음 조회에는 직접적인 프루닝 효과가 없을 수 있습니다.

WHERE customer_id = 'M001';

반대로 고객ID 해시로 파티션했다면 특정 고객의 데이터 위치를 좁히기 쉬울 수 있습니다.

하지만 특정 월 전체 주문을 찾을 때는 여러 파티션을 읽어야 할 수 있습니다.

따라서 파티션 키는 다음 질문에서 결정해야 합니다.

가장 자주 범위를 제한하는 조건은 무엇인가?


9장. 목록 파티셔닝은 특정 값 그룹으로 나눈다#

지역에 따라 데이터를 나눈다고 하겠습니다.

서울
경기
→ 수도권 파티션

부산
울산
경남
→ 동남권 파티션

이런 방식이 목록 파티셔닝입니다.

개념적으로 다음과 같습니다.

flowchart TD
    C["CUSTOMER"] --> A["수도권"]
    C --> B["충청권"]
    C --> D["영남권"]
    C --> E["호남권"]

지역 단위 업무가 명확하다면 유용할 수 있습니다.

하지만 새 지역 코드나 예상하지 못한 값이 들어올 때 어느 파티션으로 보낼지도 정해야 합니다.


10장. 해시 파티셔닝은 값을 해시해 여러 구간으로 분산한다#

고객ID를 해시해 네 파티션으로 나눈다고 하겠습니다.

hash(customer_id) % 4

결과가:

0 → P0
1 → P1
2 → P2
3 → P3

처럼 매핑됩니다.

flowchart TD
    C["customer_id"] --> H["HASH"]
    H --> P0["Partition 0"]
    H --> P1["Partition 1"]
    H --> P2["Partition 2"]
    H --> P3["Partition 3"]

해시는 데이터를 비교적 고르게 분산하기 위해 사용할 수 있습니다.

하지만 다음과 같은 날짜 범위 조회에는 물리적인 순서가 맞지 않을 수 있습니다.

2026년 9월 주문

9월 주문이 여러 해시 파티션에 흩어질 수 있기 때문입니다.


11장. 해시를 사용한다고 데이터가 완벽하게 균등해지는 것은 아니다#

다음과 같이 생각하기 쉽습니다.

해시니까 무조건 균등하게 나뉜다.

하지만 실제 부하는 행 개수만으로 결정되지 않습니다.

고객 M001의 주문이 100만 건이고 다른 고객은 각각 10건뿐이라면 특정 키가 매우 큰 비중을 차지할 수 있습니다.

요청량도 마찬가지입니다.

데이터 행 수는 비슷하게 분산되어 있어도 유명 판매자 한 곳에 트래픽이 집중되면 특정 위치의 CPU와 I/O가 과부하될 수 있습니다.

즉 분산에서 중요한 것은:

데이터량

요청량

쓰기량

CPU 작업량

저장 공간

모두입니다.


12장. 복합 파티셔닝은 두 가지 기준을 함께 사용할 수 있다#

월별 데이터 관리도 필요하고 한 달 안에서 데이터가 너무 많다고 하겠습니다.

먼저 날짜로 범위를 나눕니다.

2026-01
2026-02
2026-03

그리고 각 월 안에서 고객ID 해시로 다시 나눌 수 있습니다.

flowchart TD
    O["ORDERS"] --> J["2026-01"]
    O --> F["2026-02"]

    J --> J0["Hash 0"]
    J --> J1["Hash 1"]
    J --> J2["Hash 2"]
    J --> J3["Hash 3"]

    F --> F0["Hash 0"]
    F --> F1["Hash 1"]
    F --> F2["Hash 2"]
    F --> F3["Hash 3"]

기간별 관리와 각 기간 안의 분산을 동시에 노리는 구조입니다.

하지만 파티션 수가 크게 늘어날 수 있으므로 관리 복잡도도 증가합니다.


13장. 파티션 수가 많다고 성능이 계속 좋아지는 것은 아니다#

파티션을 12개에서 120개로 늘리면 각 파티션은 작아집니다.

그렇다고 모든 조회가 10배 빨라지지는 않습니다.

파티션이 많아지면 다음 관리 비용도 증가할 수 있습니다.

메타데이터 관리

통계 관리

인덱스 관리

백업과 복구

파티션 생성

파티션 삭제

실행 계획 복잡도

그리고 SQL이 매번 120개 파티션을 모두 읽는다면 파티션을 잘게 나눈 이점도 제한적입니다.


14장. PostgreSQL에서 월별 파티션을 만들어 보자#

간단한 예를 보겠습니다.

CREATE TABLE p_orders (
    order_id BIGINT,
    order_date DATE NOT NULL,
    amount INTEGER
)
PARTITION BY RANGE (order_date);

8월 파티션입니다.

CREATE TABLE p_orders_2026_08
PARTITION OF p_orders
FOR VALUES FROM ('2026-08-01')
TO ('2026-09-01');

9월 파티션입니다.

CREATE TABLE p_orders_2026_09
PARTITION OF p_orders
FOR VALUES FROM ('2026-09-01')
TO ('2026-10-01');

데이터를 넣습니다.

INSERT INTO p_orders
VALUES
    (1, '2026-08-31', 100),
    (2, '2026-09-01', 200);

각 행이 어느 파티션에 들어갔는지 확인할 수 있습니다.

SELECT
    tableoid::regclass,
    order_id
FROM p_orders
ORDER BY order_id;

15장. 실행 계획에서 프루닝을 확인해야 한다#

9월 주문만 조회합니다.

EXPLAIN
SELECT *
FROM p_orders
WHERE order_date >= DATE '2026-09-01'
  AND order_date < DATE '2026-10-01';

정상적으로 프루닝되면 9월 파티션 중심의 계획을 기대할 수 있습니다.

중요한 것은 결과가 9월 데이터라는 사실이 아닙니다.

실제로 8월 파티션을 읽기 대상에서 제외했는지를 보는 것입니다.


16장. 파티션 키에 함수를 적용하면 주의해야 한다#

다음 조건을 생각해 보겠습니다.

WHERE EXTRACT(MONTH FROM order_date) = 9;

의미상 9월 데이터를 찾는 조건입니다.

하지만 원래 파티션 경계는 다음과 같습니다.

2026-09-01 이상
2026-10-01 미만

함수 표현식에서 원래 날짜 범위를 바로 추론할 수 있는지는 DBMS와 버전, 최적화 기능에 따라 달라질 수 있습니다.

따라서 가능하면 파티션 키의 실제 범위와 잘 맞는 조건을 사용하는 편이 명확합니다.

또 단순히 MONTH = 9만 사용하면 여러 연도의 9월이 모두 포함될 수 있습니다.


17장. 월별 파티션의 장점은 조회뿐 아니라 데이터 수명 관리에도 있다#

2023년 주문을 모두 삭제해야 한다고 하겠습니다.

하나의 거대한 테이블에서 수억 행을 DELETE하면 큰 작업이 될 수 있습니다.

기간별 파티션으로 명확히 분리되어 있다면 오래된 파티션을 관리 단위로 활용할 수 있습니다.

개념적으로:

2023-01
2023-02
...
2023-12

의 각 구간을 보관·이동·삭제 대상으로 관리할 수 있습니다.

이런 운영 목적은 파티셔닝의 중요한 장점 중 하나입니다.


18장. 파티션 경계에는 시간대도 고려해야 한다#

주문시각을 UTC로 저장한다고 하겠습니다.

하지만 업무 매출 기준은 한국 시간일 수 있습니다.

UTC 2026-09-30 15:30
=
한국 시간 2026-10-01 00:30

날짜 기준 파티션을 UTC 날짜로 만들었는데 회계 업무는 한국 날짜를 사용한다면 월별 경계가 서로 다르게 보일 수 있습니다.

따라서 다음을 명확하게 정해야 합니다.

어떤 시간대를 기준으로 파티션할 것인가?

업무 날짜와 저장 시간은 같은가?

월 마감 기준은 무엇인가?

19장. 파티션 키는 기본키와 유일성 제약에도 영향을 줄 수 있다#

주문ID는 전역적으로 유일해야 한다고 하겠습니다.

그런데 주문일을 기준으로 파티션했습니다.

일부 DBMS에서는 파티션된 테이블의 고유 제약이나 기본키가 파티션 키와 어떤 관계를 가져야 하는지 제약이 있을 수 있습니다.

따라서 단순히 파티션만 추가하고 끝내면 안 됩니다.

다음도 확인해야 합니다.

주문ID의 전역 유일성을 어떻게 보장하는가?

인덱스는 파티션별인가 전체 범위인가?

외래키는 어떻게 검사되는가?

제품별 구현 차이가 큰 영역입니다.


20장. 샤딩은 데이터가 실제로 다른 노드에 흩어진다#

샤딩을 개념적으로 단순화하면 다음과 같습니다.

flowchart TD
    APP["Application"] --> ROUTER["Shard Router"]
    ROUTER --> S1["Shard 1"]
    ROUTER --> S2["Shard 2"]
    ROUTER --> S3["Shard 3"]

각 샤드는 서로 다른 데이터 일부를 가지고 있습니다.

예를 들어 고객ID 해시로 분산하면:

M001 → Shard 2
M002 → Shard 1
M003 → Shard 3

처럼 저장 위치를 정할 수 있습니다.

이제 요청을 처리하려면 먼저 어느 샤드로 보낼지 결정해야 합니다.


21장. 샤드 키는 데이터의 주소를 정한다#

고객ID가 샤드 키라고 하겠습니다.

hash(customer_id)
→ shard

M001의 주문은 같은 샤드에 모으도록 설계할 수 있습니다.

flowchart LR
    M1["M001"] --> S2["Shard 2"]
    S2 --> O1["O101"]
    S2 --> O2["O145"]
    S2 --> O3["O982"]

이 구조는 다음 조회에 유리할 수 있습니다.

M001의 전체 주문 이력

샤드 키를 알면 한 노드로 요청을 보낼 수 있기 때문입니다.


22장. 고객ID 샤딩은 고객별 조회에는 좋지만 전체 집계에는 불리할 수 있다#

고객ID 기준으로 네 샤드가 있다고 하겠습니다.

Shard 1
Shard 2
Shard 3
Shard 4

고객 M001 주문은 한 샤드에서 찾을 수 있습니다.

하지만 다음 질의는 어떨까요?

2026년 9월 전체 매출을 계산하라.

모든 고객의 주문이 여러 샤드에 분산되어 있습니다.

따라서 각 샤드에서 부분 합계를 계산한 뒤 합쳐야 할 수 있습니다.

flowchart TD
    Q["9월 전체 매출"] --> S1["Shard 1 부분합계"]
    Q --> S2["Shard 2 부분합계"]
    Q --> S3["Shard 3 부분합계"]
    Q --> S4["Shard 4 부분합계"]

    S1 --> M["최종 합계"]
    S2 --> M
    S3 --> M
    S4 --> M

샤드를 나눴다고 집계 자체가 사라지는 것은 아닙니다.


23장. Scatter-Gather는 여러 샤드에 요청을 뿌리고 결과를 모으는 방식이다#

조회 조건만으로 특정 샤드를 결정할 수 없으면 여러 샤드에 요청을 보낼 수 있습니다.

Scatter
→ 여러 샤드에 요청

Gather
→ 결과 수집

예를 들어 고객 조건 없는 주문 검색이라면 여러 샤드를 조회해야 할 수 있습니다.

이 구조에서는 가장 느린 샤드가 전체 응답 시간을 끌어내릴 수 있습니다.

또 결과를 모아 정렬하거나 LIMIT를 적용하는 추가 작업도 필요합니다.


24장. ORDER BY와 LIMIT도 샤드가 여러 개면 복잡해진다#

다음 요청을 생각해 보겠습니다.

전체 고객의 최근 주문 20건을 보여 달라.

각 샤드에서 최근 주문 일부를 가져올 수 있습니다.

하지만 전체 최신 20건을 구하려면 결과를 다시 합쳐 정렬해야 합니다.

flowchart TD
    Q["최근 주문 20건"] --> A["Shard A 최근 주문"]
    Q --> B["Shard B 최근 주문"]
    Q --> C["Shard C 최근 주문"]

    A --> M["병합 정렬"]
    B --> M
    C --> M

    M --> R["전체 최신 20건"]

샤드 하나에서 LIMIT 20을 실행하는 것과 여러 샤드에서 전체 LIMIT 20을 구하는 것은 다른 문제입니다.


25장. 샤드 키를 주문일로 잡으면 최근 쓰기가 몰릴 수 있다#

기간 범위로 샤딩한다고 하겠습니다.

2025 주문 → Shard 1
2026 상반기 → Shard 2
2026 하반기 → Shard 3

날짜 범위 조회는 직관적입니다.

하지만 현재 주문은 모두 최신 범위로 들어갑니다.

즉 모든 신규 쓰기가 Shard 3에 집중될 수 있습니다.

flowchart TD
    N["신규 주문"] --> S3["최신 날짜 Shard"]
    N --> S3
    N --> S3
    N --> S3

이런 현상을 핫스팟 문제로 볼 수 있습니다.


26장. 고객ID 해시도 핫스팟을 완전히 막지 못한다#

고객ID를 해시하면 고객들이 여러 샤드로 분산됩니다.

하지만 특정 고객 M999가 매우 큰 기업 고객이라고 하겠습니다.

하루 주문이 다음과 같습니다.

M999
→ 1,000,000건

일반 고객
→ 평균 2건

M999 주문은 한 샤드에 집중됩니다.

해시 함수가 정상적으로 작동해도 하나의 키 자체가 지나치게 큰 경우에는 특정 샤드가 뜨거워질 수 있습니다.

따라서 샤드 키 평가에서는 고유값 수뿐 아니라 키별 부하 분포를 봐야 합니다.


27장. 지역을 샤드 키로 사용할 수도 있다#

서비스가 지역 독립적으로 운영된다고 하겠습니다.

서울
부산
대전
광주

지역별로 데이터를 분리할 수 있습니다.

장점은 지역 기반 서비스나 데이터 거주 요건을 관리하기 쉽다는 점입니다.

하지만 지역별 사용자 수가 크게 다르면 부하도 불균형해질 수 있습니다.

또 고객이 지역을 변경하면 데이터 위치를 옮겨야 할 수도 있습니다.


28장. 좋은 샤드 키는 잘 바뀌지 않아야 한다#

샤드 키가 고객ID라고 하겠습니다.

중복 회원을 통합하면서:

M099
→
M001

로 변경해야 한다고 생각해 보겠습니다.

샤드 위치가 고객ID 해시에 따라 결정된다면 두 ID가 서로 다른 샤드에 있을 수 있습니다.

단순히 고객ID 컬럼 값만 UPDATE하면 끝나는 문제가 아닙니다.

관련 데이터를 다른 노드로 이동해야 할 수 있습니다.

기존 샤드
↓
데이터 이동
↓
새 샤드

그래서 샤드 키는 가능하면 안정적인 값이 좋습니다.


29장. 샤드 키 변경은 데이터 마이그레이션 문제다#

M099의 주문이 500만 건 있다고 하겠습니다.

회원 통합 후 M001 위치로 옮겨야 한다면 대량 이동이 필요할 수 있습니다.

이동 중에는 다음 문제를 정해야 합니다.

기존 샤드를 읽을 것인가?

새 샤드를 읽을 것인가?

신규 주문은 어디에 쓸 것인가?

이동 도중 중복 데이터는 허용할 것인가?

실패하면 어디서 다시 시작할 것인가?

샤드 키가 저장 위치를 결정한다는 의미는 이런 운영 문제까지 포함합니다.


30장. 리샤딩은 샤드 수를 바꾸는 작업이다#

처음에는 네 개 샤드로 충분했다고 하겠습니다.

Shard 1
Shard 2
Shard 3
Shard 4

서비스가 성장해 여덟 개로 늘려야 합니다.

Shard 1
...
Shard 8

기존 데이터의 일부를 새로운 노드로 이동해야 할 수 있습니다.

단순한 MOD 4 방식에서 MOD 8로 바꾸면 많은 키의 위치가 달라질 수 있습니다.

따라서 대규모 분산 시스템에서는 데이터 이동량을 줄이기 위한 여러 배치 전략이 사용되기도 합니다.

샤딩 설계에서는 최초 배치뿐 아니라 확장 시 재분배 방식까지 생각해야 합니다.


31장. 샤드 키는 트랜잭션 경계에도 영향을 준다#

고객ID 기준으로 주문을 샤딩했다고 하겠습니다.

M001의 주문과 결제가 같은 샤드에 있습니다.

그러면 고객 한 명 안의 일부 작업을 한 노드에서 처리하기 쉬울 수 있습니다.

하지만 재고는 상품ID 기준으로 다른 샤드에 있다고 하겠습니다.

주문
→ 고객ID 샤드

재고
→ 상품ID 샤드

주문 생성 시 다음 두 작업을 함께 처리해야 합니다.

주문 생성

재고 차감

서로 다른 노드를 건드릴 수 있습니다.


32장. 고객 주문이 한 샤드에 있다는 것과 주문 처리가 한 샤드에서 끝난다는 것은 다르다#

다음 문장은 맞을 수 있습니다.

고객 M001의 주문은 모두 한 샤드에 있다.

하지만 다음 문장은 별개의 문제입니다.

고객 M001의 주문 처리는 모두 한 샤드 안에서 끝난다.

상품 재고, 포인트, 쿠폰, 결제 데이터가 다른 기준으로 분산되어 있다면 주문 처리 전체는 여러 노드를 사용할 수 있습니다.

따라서 샤딩 설계에서는 한 업무 트랜잭션에 참여하는 데이터 전체를 봐야 합니다.


33장. 교차 샤드 트랜잭션은 복잡도가 높아진다#

노드 A에서 주문을 만들고 노드 B에서 재고를 줄인다고 하겠습니다.

주문은 성공했는데 재고 차감이 실패하면 어떻게 해야 할까요?

다음 선택지가 생깁니다.

전체 원자성 보장

재시도

보상 트랜잭션

일시적 불일치 허용

분산 트랜잭션을 지원하는 시스템이라면 여러 노드의 확정을 조정할 수도 있습니다.

하지만 네트워크 장애와 부분 실패까지 고려해야 합니다.

샤딩은 저장 공간을 나누는 문제에서 끝나지 않습니다.


34장. 교차 샤드 JOIN은 불가능한 것이 아니라 비싸질 수 있다#

다음과 같이 흔히 말하기도 합니다.

샤딩하면 JOIN을 할 수 없다.

너무 단정적인 표현입니다.

제품이나 시스템 구조에 따라 교차 노드 JOIN을 지원할 수 있습니다.

문제는 비용입니다.

데이터를 한 노드로 이동하거나 여러 노드에서 부분 결과를 가져와 결합해야 할 수 있습니다.

flowchart LR
    S1["Shard 1"] --> C["Coordinator"]
    S2["Shard 2"] --> C
    S3["Shard 3"] --> C
    C --> J["Join / Aggregate"]

네트워크와 데이터 이동 비용이 추가됩니다.


35장. 전역적으로 유일한 ID도 생각해야 한다#

여러 샤드에서 동시에 주문을 생성한다고 하겠습니다.

각 샤드가 단순히 다음과 같은 번호를 만든다면:

1
2
3

다른 샤드에서도 같은 번호가 생길 수 있습니다.

Shard 1 → 주문 100

Shard 2 → 주문 100

전역 주문ID가 유일해야 한다면 별도의 ID 생성 전략이 필요합니다.

예를 들면 시스템에 따라 다음 방법을 고려할 수 있습니다.

UUID

샤드 정보를 포함한 ID

분산 ID 생성기

DBMS가 제공하는 분산 키

36장. 파티셔닝과 샤딩을 함께 사용할 수도 있다#

두 방식은 서로 배타적이지 않습니다.

고객ID로 네 개 샤드를 만든다고 하겠습니다.

Shard A
Shard B
Shard C
Shard D

각 샤드 안에서 주문을 다시 월별 파티션으로 관리할 수 있습니다.

flowchart TD
    S1["Shard A"] --> A1["2026-08"]
    S1 --> A2["2026-09"]

    S2["Shard B"] --> B1["2026-08"]
    S2 --> B2["2026-09"]

이 구조에서는:

고객ID
→ 어느 노드인지 결정

주문일
→ 노드 내부 어느 파티션인지 결정

합니다.


37장. 고객별 샤딩과 월별 파티셔닝을 결합한 조회#

고객 M001의 2026년 9월 주문을 조회한다고 하겠습니다.

먼저 고객ID로 샤드를 찾습니다.

M001
↓
Shard B

그다음 날짜 조건으로 9월 파티션을 찾습니다.

Shard B
↓
2026-09 Partition

조회 범위가 매우 좁아질 수 있습니다.

반면 전체 고객의 2026년 9월 매출은 모든 샤드의 9월 파티션을 읽어야 합니다.

Shard A / 9월
Shard B / 9월
Shard C / 9월
Shard D / 9월

같은 구조에서도 질의에 따라 방문 범위가 달라집니다.


38장. 분할 키를 선택할 때는 대표 조회를 표로 만들어야 한다#

예를 들어 다음 업무가 있다고 하겠습니다.

업무 빈도
고객 한 명의 주문 조회 매우 높음
최근 하루 전체 주문 조회 중간
월매출 집계 중간
과거 월 삭제 낮지만 중요
고객 데이터 이동 매우 낮음

이 표를 기준으로 분할 키 후보를 비교할 수 있습니다.


39장. 고객ID를 분할 키로 선택하면#

장점:

고객 한 명의 데이터를 한 위치에 모으기 쉬움

고객별 주문 조회가 단일 위치에서 끝날 가능성이 높음

고객 단위 변경을 함께 처리하기 쉬움

단점:

전체 기간 집계는 여러 위치 필요

큰 고객이 핫스팟이 될 수 있음

고객ID 변경이 데이터 이동으로 이어질 수 있음

40장. 날짜를 분할 키로 선택하면#

장점:

기간 조회가 자연스러움

오래된 데이터 관리가 편함

월별 보관 정책 적용이 쉬움

단점:

한 고객의 전체 이력이 여러 구간에 흩어짐

최근 기간으로 쓰기가 집중될 수 있음

기간을 넘는 조회는 여러 조각 접근

41장. 지역을 분할 키로 선택하면#

장점:

지역별 서비스 분리

지역별 운영

데이터 거주 요구 대응 가능

단점:

지역별 데이터량 불균형

지역 변경 시 이동

전국 단위 통계의 교차 조회

어떤 키도 모든 업무에 최적이지 않습니다.


42장. 파티션 프루닝과 샤드 라우팅은 비슷해 보여도 역할이 다르다#

파티션 프루닝은 DBMS가 필요 없는 파티션을 실행 계획에서 제외하는 것입니다.

SQL 조건
↓
파티션 경계 판단
↓
필요한 파티션만 접근

샤드 라우팅은 요청을 데이터가 존재하는 노드로 보내는 과정입니다.

샤드 키
↓
샤드 위치 결정
↓
해당 노드로 요청

둘 다 조회 범위를 줄이지만 하나는 파티션 선택이고 다른 하나는 노드 선택입니다.


43장. 프루닝에 성공해도 조회가 느릴 수 있다#

12개 파티션 중 하나만 읽게 되었다고 하겠습니다.

좋은 결과입니다.

하지만 그 한 파티션에 5억 건이 있다면 여전히 큰 데이터입니다.

다음 단계가 필요할 수 있습니다.

인덱스

추가 필터

적절한 조인 순서

집계 방식 개선

병렬 처리

파티션 프루닝은 불필요한 파티션을 제거할 뿐, 남은 데이터에 대한 모든 성능 문제를 해결하지 않습니다.


44장. 샤드가 많아도 모든 요청이 모든 샤드를 방문하면 효과가 제한된다#

샤드가 100개라고 하겠습니다.

그런데 핵심 조회가 매번 100개 샤드에 요청을 보냅니다.

1개 요청
↓
100개 샤드 호출
↓
100개 응답 대기
↓
결과 병합

저장 용량은 분산되었지만 조회 조정 비용은 커집니다.

특히 한 샤드가 느리거나 장애가 나면 전체 요청에 영향을 줄 수 있습니다.

따라서 좋은 샤드 키는 가능한 많은 핵심 요청을 적은 수의 샤드로 라우팅할 수 있게 하는 키입니다.


45장. 샤드 수가 많아질수록 운영 문제도 늘어난다#

샤드가 하나일 때는 하나의 데이터베이스를 관리하면 됩니다.

샤드가 100개라면 다음도 100개 단위로 생각해야 할 수 있습니다.

배포

스키마 변경

백업

복구

모니터링

장애 대응

용량 계획

통계 수집

분산은 저장 공간뿐 아니라 운영 복잡도도 분산시킵니다.


46장. 스키마 변경도 여러 샤드에 적용해야 한다#

주문 테이블에 새로운 컬럼을 추가한다고 하겠습니다.

ALTER TABLE orders
ADD COLUMN channel VARCHAR(20);

샤드가 여러 개라면 모든 노드에 동일한 변경이 적용되어야 합니다.

일부 샤드만 새 스키마가 되고 일부는 이전 스키마라면 애플리케이션이 서로 다른 구조를 만나게 됩니다.

따라서 샤딩 환경에서는 스키마 마이그레이션 자체도 중요한 운영 문제입니다.


47장. 샤드 장애는 일부 사용자에게만 영향을 줄 수도 있다#

고객ID 기준으로 데이터를 분산했다고 하겠습니다.

Shard 3이 장애가 났습니다.

Shard 1, 2, 4의 고객은 정상일 수 있습니다.

Shard 3 고객만 주문 조회가 실패할 수도 있습니다.

즉 장애 영향 범위가 특정 샤드로 제한될 수 있다는 장점이 있습니다.

반대로 전체 집계처럼 모든 샤드가 필요한 업무는 Shard 3 하나의 장애로도 완전한 결과를 만들지 못할 수 있습니다.


48장. 일부 샤드가 실패했을 때 집계 결과를 어떻게 표시할지도 정해야 한다#

월매출 집계가 네 샤드를 읽는다고 하겠습니다.

Shard A → 1억
Shard B → 2억
Shard C → 응답 실패
Shard D → 1억

이때 단순히:

총매출 = 4억

이라고 표시하면 틀린 결과입니다.

다음과 같이 처리할 수 있습니다.

집계 실패

부분 결과임을 표시

재시도

이전 확정값 사용

분산 환경에서는 부분 실패가 정상적으로 발생할 수 있다는 가정이 필요합니다.


49장. 샤드 키를 선택할 때 확인해야 할 핵심 조건#

좋은 샤드 키를 평가할 때는 다음을 봅니다.

분산성#

데이터와 요청이 한 곳에 몰리지 않는가?

지역성#

핵심 업무가 한 샤드 안에서 끝나는가?

안정성#

키가 자주 변경되지 않는가?

카디널리티#

충분한 값 종류가 있는가?

핫키 위험#

특정 하나의 키에 부하가 지나치게 몰리지 않는가?

확장성#

샤드 수가 늘어날 때 데이터 이동을 관리할 수 있는가?

트랜잭션 경계#

함께 변경할 데이터가 여러 샤드로 찢어지지 않는가?

샤드 키는 단순한 분배 기준이 아니라 시스템의 운영 구조를 결정합니다.


50장. 파티셔닝과 샤딩 차이를 한눈에 정리하면#

구분 파티셔닝 샤딩
기본 개념 논리 데이터 내부 분할 여러 노드로 데이터 분산
주요 목적 조회 범위·보존·공간 관리 저장 용량·부하의 노드 분산
위치 결정 파티션 규칙 샤드 키와 라우팅
범위 축소 파티션 프루닝 단일 샤드 라우팅
여러 구간 조회 여러 파티션 접근 여러 노드 요청
집계 파티션 간 집계 샤드 간 집계와 결과 병합
키 변경 다른 파티션으로 이동 가능 노드 간 데이터 이동 가능
장애 영향 주로 DBMS 내부 관리 일부 노드 장애와 부분 실패 고려
운영 난이도 파티션·인덱스·경계 관리 배포·복제·라우팅·재분산까지 관리

실제 분산 DBMS에서는 내부적으로 파티션과 샤드를 함께 사용하기도 하므로 제품 용어와 구현을 따로 확인해야 합니다.


51장. 분할 전 반드시 해야 할 질문#

테이블이 커졌다는 이유만으로 바로 파티셔닝하거나 샤딩하지 않는 것이 좋습니다.

다음 질문부터 확인해야 합니다.

  1. 실제로 어떤 조회가 느린가?
  2. 그 조회는 어느 조건으로 범위를 좁히는가?
  3. 인덱스만으로 해결할 수 없는가?
  4. 오래된 데이터의 보관과 삭제 단위는 무엇인가?
  5. 대부분의 요청은 고객 중심인가 날짜 중심인가?
  6. 어떤 데이터가 한 트랜잭션에서 함께 변경되는가?
  7. 특정 키에 트래픽이 몰릴 가능성이 있는가?
  8. 분할 키는 변경될 수 있는가?
  9. 전체 집계는 얼마나 자주 실행되는가?
  10. 분할 후 몇 개의 파티션이나 샤드를 방문하게 되는가?
  11. 샤드 수를 늘릴 때 어떻게 재분배할 것인가?
  12. 부분 장애 시 어떤 결과를 사용자에게 보여줄 것인가?

분할의 효과는 이 질문에 대한 답에서 결정됩니다.


52장. 파티션과 샤드 수 자체가 목표가 되어서는 안 된다#

다음과 같은 설명은 의미가 부족합니다.

파티션을 100개로 만들었다.

샤드를 32개로 늘렸다.

중요한 것은 숫자가 아닙니다.

다음처럼 설명할 수 있어야 합니다.

9월 주문 조회는 36개 월 파티션 중 1개만 접근한다.

고객 주문 조회의 98%는 단일 샤드에서 끝난다.

전체 매출 보고서는 8개 샤드의 부분 집계를 합친다.

가장 큰 고객이 특정 샤드 CPU의 40%를 사용한다.

이런 정보가 실제 설계 품질을 보여줍니다.


53장. 파티셔닝과 샤딩은 성능보다 데이터 이동을 먼저 생각해야 할 때도 있다#

데이터를 나누는 순간 한 행의 위치는 분할 키와 연결됩니다.

따라서 다음 변화가 발생하면 위치가 바뀔 수 있습니다.

날짜 경계 변경

고객ID 변경

지역 변경

샤드 수 증가

키 분배 규칙 변경

데이터가 수십억 건이라면 이동 자체가 매우 큰 작업입니다.

따라서 분할 설계에서는 현재 조회뿐 아니라 데이터가 앞으로 어떻게 움직일 수 있는가도 봐야 합니다.


54장. 핵심 정리#

파티셔닝과 샤딩은 모두 큰 데이터를 여러 조각으로 나누는 기술입니다.

하지만 같은 개념은 아닙니다.

파티셔닝은 하나의 논리적인 데이터 집합을 범위·목록·해시 등의 규칙으로 나누어 관리합니다.

샤딩은 데이터를 여러 저장 노드에 나누어 배치하고 요청을 적절한 노드로 라우팅합니다.

파티셔닝의 중요한 최적화가 파티션 프루닝입니다.

조회 조건
↓
파티션 경계 분석
↓
필요 없는 파티션 제외

하지만 결과 행이 적다고 프루닝이 된 것은 아닙니다.

실행 계획에서 실제 접근 파티션을 확인해야 합니다.

샤딩에서는 샤드 키가 매우 중요합니다.

고객ID
주문일
지역

어떤 값을 선택하느냐에 따라 핵심 업무가 한 노드에서 끝날 수도 있고 모든 노드를 돌아다녀야 할 수도 있습니다.

그리고 샤드 키는 조회 경로만 결정하지 않습니다.

데이터 분포

쓰기 집중

핫스팟

트랜잭션 경계

교차 샤드 JOIN

전체 집계

키 변경 시 데이터 이동

리샤딩

장애 영향 범위

까지 영향을 줍니다.

따라서 좋은 분할 키를 찾는 기준은 단순히 데이터가 고르게 나뉘는지가 아닙니다.

가장 중요한 업무가 얼마나 적은 파티션과 샤드 안에서 끝날 수 있는가?

그리고 동시에 다음을 물어야 합니다.

그 선택 때문에 어떤 다른 업무가 여러 구간을 방문하게 되는가?

파티셔닝과 샤딩은 데이터를 작게 만드는 마법이 아닙니다.

데이터가 어디에 놓이고, 어떤 요청이 어느 범위까지 움직여야 하는지를 다시 설계하는 작업입니다.

이 페이지의 목차