Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

단일 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 | 데이터 지역성 필요 | 불균형 분산 가능 |

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

**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 기능의 의미는?**

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

현재 단락 (1/249)

단일 MongoDB 인스턴스가 감당할 수 없는 데이터량이나 처리량에 도달하면 **Sharding**이 필요합니다. Sharding은 데이터를 여러 서버(shard)에 수평 분산하여...

작성 글자: 0원문 글자: 7,133작성 단락: 0/249