Skip to content
Published on

MongoDB Sharding 완벽 가이드: 샤드 키 설계부터 운영 자동화까지

Authors
  • Name
    Twitter

들어가며

단일 MongoDB 인스턴스가 감당할 수 없는 데이터량이나 처리량에 도달하면 Sharding이 필요합니다. Sharding은 데이터를 여러 서버(shard)에 수평 분산하여 읽기/쓰기 성능과 저장 용량을 확장하는 기술입니다.

이 글에서는 MongoDB 7.0+ 기준으로 Sharded Cluster를 구축하고 운영하는 과정을 다룹니다.

Sharded Cluster 아키텍처

MongoDB Sharded Cluster는 세 가지 컴포넌트로 구성됩니다:

  • Shard: 실제 데이터를 저장하는 Replica Set
  • Config Server: 메타데이터와 라우팅 정보를 저장하는 Replica Set
  • mongos: 클라이언트 요청을 적절한 Shard로 라우팅하는 라우터

Docker Compose로 Sharded Cluster 구축

# docker-compose.yml
services:
  # Config Server Replica Set
  config-svr-1:
    image: mongo:7.0
    command: mongod --configsvr --replSet configRS --port 27019
    volumes:
      - config1-data:/data/db

  config-svr-2:
    image: mongo:7.0
    command: mongod --configsvr --replSet configRS --port 27019
    volumes:
      - config2-data:/data/db

  config-svr-3:
    image: mongo:7.0
    command: mongod --configsvr --replSet configRS --port 27019
    volumes:
      - config3-data:/data/db

  # Shard 1 Replica Set
  shard1-1:
    image: mongo:7.0
    command: mongod --shardsvr --replSet shard1RS --port 27018
    volumes:
      - shard1-1-data:/data/db

  shard1-2:
    image: mongo:7.0
    command: mongod --shardsvr --replSet shard1RS --port 27018
    volumes:
      - shard1-2-data:/data/db

  # Shard 2 Replica Set
  shard2-1:
    image: mongo:7.0
    command: mongod --shardsvr --replSet shard2RS --port 27018
    volumes:
      - shard2-1-data:/data/db

  shard2-2:
    image: mongo:7.0
    command: mongod --shardsvr --replSet shard2RS --port 27018
    volumes:
      - shard2-2-data:/data/db

  # Mongos Router
  mongos:
    image: mongo:7.0
    command: mongos --configdb configRS/config-svr-1:27019,config-svr-2:27019,config-svr-3:27019 --port 27017
    ports:
      - '27017:27017'
    depends_on:
      - config-svr-1
      - config-svr-2
      - config-svr-3

volumes:
  config1-data:
  config2-data:
  config3-data:
  shard1-1-data:
  shard1-2-data:
  shard2-1-data:
  shard2-2-data:

초기화 스크립트

#!/bin/bash
# init-sharding.sh

# 1. Config Server Replica Set 초기화
docker exec -it config-svr-1 mongosh --port 27019 --eval '
rs.initiate({
  _id: "configRS",
  configsvr: true,
  members: [
    { _id: 0, host: "config-svr-1:27019" },
    { _id: 1, host: "config-svr-2:27019" },
    { _id: 2, host: "config-svr-3:27019" }
  ]
})'

# 2. Shard 1 Replica Set 초기화
docker exec -it shard1-1 mongosh --port 27018 --eval '
rs.initiate({
  _id: "shard1RS",
  members: [
    { _id: 0, host: "shard1-1:27018" },
    { _id: 1, host: "shard1-2:27018" }
  ]
})'

# 3. Shard 2 Replica Set 초기화
docker exec -it shard2-1 mongosh --port 27018 --eval '
rs.initiate({
  _id: "shard2RS",
  members: [
    { _id: 0, host: "shard2-1:27018" },
    { _id: 1, host: "shard2-2:27018" }
  ]
})'

sleep 10

# 4. Shard를 클러스터에 추가
docker exec -it mongos mongosh --eval '
sh.addShard("shard1RS/shard1-1:27018,shard1-2:27018");
sh.addShard("shard2RS/shard2-1:27018,shard2-2:27018");
'

샤드 키 전략

샤드 키 선택은 Sharding 성능의 80%를 결정합니다.

1. Range Sharding

// 범위 기반 샤딩 - 연속 데이터 조회에 유리
use mydb;
sh.enableSharding("mydb");

// created_at을 샤드 키로 사용
db.orders.createIndex({ created_at: 1 });
sh.shardCollection("mydb.orders", { created_at: 1 });

// 장점: 날짜 범위 쿼리가 단일 샤드에서 처리
db.orders.find({
  created_at: {
    $gte: ISODate("2026-03-01"),
    $lt: ISODate("2026-04-01")
  }
});

// 단점: 최신 데이터가 하나의 샤드에 집중 (핫스팟)

2. Hashed Sharding

// 해시 기반 샤딩 - 균등 분산에 유리
db.users.createIndex({ user_id: 'hashed' })
sh.shardCollection('mydb.users', { user_id: 'hashed' })

// 장점: 쓰기가 모든 샤드에 균등 분산
// 단점: 범위 쿼리 시 모든 샤드를 스캔 (scatter-gather)

// 사전 청크 분할로 초기 불균형 방지
sh.shardCollection('mydb.events', { event_id: 'hashed' }, false, {
  numInitialChunks: 64,
})

3. Compound Shard Key

// 복합 샤드 키 - 가장 권장되는 패턴
db.orders.createIndex({ customer_id: 1, order_date: 1 })
sh.shardCollection('mydb.orders', { customer_id: 1, order_date: 1 })

// customer_id로 카디널리티 확보
// order_date로 범위 쿼리 최적화

// 이 쿼리는 targeted (단일 샤드)
db.orders.find({
  customer_id: 'C12345',
  order_date: { $gte: ISODate('2026-01-01') },
})

4. Zone Sharding (지역 기반)

// Zone Sharding - 데이터 지역성 보장
sh.addShardToZone('shard1RS', 'APAC')
sh.addShardToZone('shard2RS', 'EU')

// Zone 범위 설정
sh.updateZoneKeyRange(
  'mydb.users',
  { region: 'KR' },
  { region: 'KS' }, // KR의 다음 문자열
  'APAC'
)

sh.updateZoneKeyRange('mydb.users', { region: 'DE' }, { region: 'DF' }, 'EU')

// 한국 사용자 데이터는 APAC 샤드에만 저장
// 독일 사용자 데이터는 EU 샤드에만 저장

샤드 키 선택 가이드

기준좋은 샤드 키나쁜 샤드 키
카디널리티높음 (user_id)낮음 (status: active/inactive)
분산도균등 분산편향 (특정 값에 집중)
쿼리 패턴자주 조건에 포함거의 사용 안 함
단조 증가피하거나 해시created_at (핫스팟)
변경 가능MongoDB 5.0+ 가능이전 버전은 변경 불가

샤드 키 변경 (MongoDB 5.0+)

// resharding - 샤드 키 변경
db.adminCommand({
  reshardCollection: 'mydb.orders',
  key: { customer_id: 1, order_date: 1 },
})

// 진행 상황 확인
db.getSiblingDB('admin').aggregate([
  { $currentOp: { allUsers: true, localOps: false } },
  { $match: { type: 'op', 'originatingCommand.reshardCollection': { $exists: true } } },
])

청크 관리

청크 분할과 마이그레이션

// 청크 상태 확인
use config;
db.chunks.find({ ns: "mydb.orders" }).sort({ min: 1 });

// 청크 수 확인
db.chunks.countDocuments({ ns: "mydb.orders" });

// 샤드별 청크 분포
db.chunks.aggregate([
  { $match: { ns: "mydb.orders" } },
  { $group: { _id: "$shard", count: { $sum: 1 } } }
]);

// 수동 청크 분할
sh.splitAt("mydb.orders", { customer_id: "C50000", order_date: ISODate("2026-01-01") });

// 수동 청크 이동
sh.moveChunk("mydb.orders",
  { customer_id: "C50000", order_date: ISODate("2026-01-01") },
  "shard2RS"
);

밸런서 관리

// 밸런서 상태 확인
sh.getBalancerState()
sh.isBalancerRunning()

// 밸런서 시간 제한 (업무 시간 외에만 실행)
db.settings.updateOne(
  { _id: 'balancer' },
  {
    $set: {
      activeWindow: { start: '02:00', stop: '06:00' },
    },
  },
  { upsert: true }
)

// 특정 컬렉션의 밸런싱 비활성화 (마이그레이션 중)
sh.disableBalancing('mydb.orders')
// 재활성화
sh.enableBalancing('mydb.orders')

쿼리 라우팅 이해

// Targeted Query - 단일 샤드에서 처리 (빠름)
// 샤드 키가 쿼리 조건에 포함
db.orders.find({ customer_id: 'C12345' }).explain('executionStats')
// "winningPlan": { "stage": "SINGLE_SHARD" }

// Scatter-Gather Query - 모든 샤드에서 처리 (느림)
// 샤드 키가 쿼리 조건에 미포함
db.orders.find({ product: 'iPhone' }).explain('executionStats')
// "winningPlan": { "stage": "SHARD_MERGE" }

// Broadcast Query - 모든 샤드에 전송
db.orders.aggregate([{ $group: { _id: '$status', total: { $sum: '$amount' } } }])

모니터링

// 샤딩 상태 전체 확인
sh.status();
sh.status(true);  // 상세 정보 포함

// 청크 마이그레이션 히스토리
use config;
db.changelog.find({ what: "moveChunk.commit" }).sort({ time: -1 }).limit(10);

// 현재 진행 중인 마이그레이션
db.locks.find({ _id: "balancer" });

// 각 샤드의 데이터 크기
db.adminCommand({ listDatabases: 1, nameOnly: false });

mongosh 유틸리티

// 컬렉션 통계
db.orders.stats()
db.orders.stats().sharded // true면 샤딩됨

// 샤드별 문서 수
db.orders.getShardDistribution()
// Shard shard1RS: data 2.5GB, docs 5,000,000, chunks 32
// Shard shard2RS: data 2.3GB, docs 4,800,000, chunks 30

운영 주의사항

1. 샤드 추가

// 새 샤드 추가 (서비스 중단 없음)
sh.addShard('shard3RS/shard3-1:27018,shard3-2:27018')

// 밸런서가 자동으로 청크를 새 샤드로 이동
// 이동 완료까지 시간이 걸림

2. 샤드 제거

// 샤드 제거 (draining)
db.adminCommand({ removeShard: 'shard2RS' })

// 진행 상황 확인 (반복 실행)
db.adminCommand({ removeShard: 'shard2RS' })
// "state": "ongoing", "remaining": { "chunks": 15, "dbs": 0 }

// 완료될 때까지 반복 확인

3. 백업 전략

# mongodump로 전체 클러스터 백업 (mongos를 통해)
mongodump --host mongos:27017 --out /backup/$(date +%Y%m%d)

# 또는 각 샤드의 Replica Set을 개별 백업
mongodump --host shard1-1:27018 --out /backup/shard1/$(date +%Y%m%d)
mongodump --host shard2-1:27018 --out /backup/shard2/$(date +%Y%m%d)

정리

전략적합한 케이스주의점
Range범위 쿼리 중심핫스팟 발생 가능
Hashed균등 분산 필요범위 쿼리 비효율
Compound다양한 쿼리 패턴키 설계에 신중해야 함
Zone데이터 지역성 필요불균형 분산 가능

샤드 키 선택은 되돌리기 어려운 결정이므로, 실 데이터와 쿼리 패턴을 충분히 분석한 후 결정하세요.


✅ 퀴즈: MongoDB Sharding 이해도 점검 (7문제)

Q1. Sharded Cluster의 세 가지 구성 요소는?

Shard (데이터 저장), Config Server (메타데이터), mongos (라우터)입니다.

Q2. Range Sharding의 핫스팟 문제란?

단조 증가하는 키(created_at 등)를 사용하면 최신 데이터가 항상 마지막 샤드에 집중됩니다.

Q3. Targeted Query와 Scatter-Gather Query의 차이는?

Targeted는 샤드 키가 쿼리 조건에 포함되어 단일 샤드에서 처리되고, Scatter-Gather는 모든 샤드를 스캔합니다.

Q4. numInitialChunks를 설정하는 이유는?

Hashed Sharding에서 초기 청크를 미리 분할하여 데이터 로드 시 불균형을 방지합니다.

Q5. Zone Sharding의 주요 사용 사례는?

데이터 거주 규정(GDPR 등)에 따라 특정 지역의 데이터를 특정 샤드에만 저장해야 할 때 사용합니다.

Q6. 밸런서의 activeWindow 설정은 왜 필요한가요?

청크 마이그레이션은 I/O를 많이 사용하므로, 업무 시간 외에만 실행하여 서비스 영향을 최소화합니다.

Q7. MongoDB 5.0+에서 추가된 resharding 기능의 의미는?

기존에는 변경 불가했던 샤드 키를 온라인 상태에서 변경할 수 있게 되었습니다.