Skip to content
Published on

MongoDB Sharding 完全ガイド:シャードキー設計から運用自動化まで

Authors
  • Name
    Twitter

はじめに

単一の 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 機能の意味は?

以前は変更不可だったシャードキーをオンライン状態で変更できるようになりました。