- Authors
- Name
はじめに
単一の MongoDB インスタンスでは対応できないデータ量やスループットに達したら、Sharding が必要になります。Sharding はデータを複数のサーバー(shard)に水平分散し、読み書きパフォーマンスとストレージ容量を拡張する技術です。
この記事では MongoDB 7.0+ を基準に、Sharded Cluster の構築と運用プロセスを解説します。
Sharded Cluster アーキテクチャ
MongoDB Sharded Cluster は3つのコンポーネントで構成されます:
- 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. クラスタにシャードを追加
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")
}
});
// 欠点: 最新データが1つのシャードに集中(ホットスポット)
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 の3つの構成要素は?
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 機能の意味は?
以前は変更不可だったシャードキーをオンライン状態で変更できるようになりました。