Skip to content
Published on

MongoDB ShardingとReplica Set運用ガイド:クラスター設計からトラブルシューティングまで

Authors
  • Name
    Twitter
MongoDB Sharding and Replica Set

はじめに

MongoDBは大規模データ処理と高可用性のために、ShardingとReplica Setという2つの核心的な分散メカニズムを提供している。Shardingはデータを複数のサーバーに水平分散してストレージ容量とスループットを拡張し、Replica Setはデータを複製して障害発生時の自動フェイルオーバーを保証する。プロダクション環境でこの2つのメカニズムを正しく組み合わせることが、安定したMongoDB運用の基盤となる。

本記事では、Shardingアーキテクチャの構成要素からShard Key選択戦略、Replica Setフェイルオーバーメカニズム、Chunkマイグレーションとバランサー管理、読み取り/書き込みConcern設定、モニタリングとパフォーマンスチューニング、バックアップ/リカバリ、そして実践的なトラブルシューティング事例まで、プロダクションクラスター運用に必要な全体知識を網羅する。

Shardingアーキテクチャ概要

MongoDB Sharded Clusterは3つの構成要素で成り立っている。

mongos(Query Router):クライアントリクエストを適切なシャードにルーティングするプロキシの役割を果たす。ステートレスなので、複数台をロードバランサーの背後にデプロイできる。

Config Server:クラスターのメタデータ(シャード一覧、チャンク範囲、データ分布情報)を格納するReplica Set。MongoDB 3.4以降、必ずReplica Setとして構成する必要がある。

Shard:実際のデータを格納するReplica Set。各シャードは全体データの一部(チャンク)を担当する。

# Sharded Cluster 基本構成(最小構成)
#
# Client
#   |
#   v
# mongos (Router) x 2  -- ロードバランサーの背後に配置
#   |
#   v
# Config Server Replica Set (3 nodes)
#   |
#   +--- Shard 1 Replica Set (Primary + 2 Secondary)
#   +--- Shard 2 Replica Set (Primary + 2 Secondary)
#   +--- Shard 3 Replica Set (Primary + 2 Secondary)
#
# 合計ノード数: 2 (mongos) + 3 (config) + 9 (shard) = 14

Sharded Cluster初期構成

// mongosに接続してShard追加
sh.addShard('shard1/mongo-shard1-a:27018,mongo-shard1-b:27018,mongo-shard1-c:27018')
sh.addShard('shard2/mongo-shard2-a:27018,mongo-shard2-b:27018,mongo-shard2-c:27018')
sh.addShard('shard3/mongo-shard3-a:27018,mongo-shard3-b:27018,mongo-shard3-c:27018')

// データベースのShardingを有効化
sh.enableSharding('myapp')

// コレクションにShard Keyを指定(Hashed)
sh.shardCollection('myapp.orders', { customerId: 'hashed' })

// コレクションにShard Keyを指定(Ranged)
sh.shardCollection('myapp.logs', { timestamp: 1 })

// クラスター状態確認
sh.status()

Shard Key選択戦略

Shard Keyは一度設定すると変更が非常に難しいため(MongoDB 5.0からreshardingサポート)、設計段階で慎重に選択する必要がある。良いShard Keyは、高いカーディナリティ(cardinality)、低い頻度(frequency)、非単調(non-monotonic)な特性を持つべきだ。

Shard Key戦略比較表

戦略説明メリットデメリット適合するユースケース
Hashed Shardingフィールド値のハッシュで分散データ均等分配、ホットスポット防止範囲クエリ非効率、全シャードスキャン必要高頻度書き込み、範囲クエリ不要
Ranged Shardingフィールド値の範囲で分散範囲クエリ効率的、特定シャードのみアクセスホットスポット可能、単調増加キー問題時間ベースログ、範囲クエリ頻繁
Zone Sharding地域/条件別データ配置データローカリティ、規制準拠設定複雑、不均衡の可能性マルチリージョン、データ主権要件
Compound Key複合フィールド組み合わせカーディナリティ向上、クエリ最適化設計複雑度増加単一フィールドでは不十分な場合

Shard Key設定例

// 1. Hashed Shard Key - 均等分配が最優先の場合
sh.shardCollection('myapp.users', { _id: 'hashed' })

// 2. Compound Shard Key - カーディナリティとクエリパターンの同時充足
sh.shardCollection('myapp.events', { tenantId: 1, createdAt: 1 })

// 3. Zone Sharding - 地域別データ分離
sh.addShardToZone('shard1', 'JP')
sh.addShardToZone('shard2', 'US')
sh.addShardToZone('shard3', 'EU')

sh.updateZoneKeyRange(
  'myapp.users',
  { region: 'JP', _id: MinKey },
  { region: 'JP', _id: MaxKey },
  'JP'
)
sh.updateZoneKeyRange(
  'myapp.users',
  { region: 'US', _id: MinKey },
  { region: 'US', _id: MaxKey },
  'US'
)

// 4. MongoDB 8.0+ resharding - Shard Key変更が必要な場合
sh.reshardCollection('myapp.orders', { customerId: 1, orderId: 1 })

Shard Keyアンチパターン

単調増加するフィールド(例:ObjectId、timestamp)をRanged Shard Keyとして使用すると、新しいデータが常に最後のチャンクに集中し、ホットシャード(Hot Shard)が発生する。この場合、Hashed Shardingを使用するか複合キーを組み合わせる必要がある。また、カーディナリティが低すぎるフィールド(例:boolean、status列挙型)はデータを均等に分散できず、ジャンボチャンク(Jumbo Chunk)を引き起こす可能性がある。

Replica Set構成とフェイルオーバー

Replica Set初期化

// PrimaryでReplica Set初期化
rs.initiate({
  _id: 'shard1',
  members: [
    { _id: 0, host: 'mongo-shard1-a:27018', priority: 10 },
    { _id: 1, host: 'mongo-shard1-b:27018', priority: 5 },
    { _id: 2, host: 'mongo-shard1-c:27018', priority: 1 },
  ],
  settings: {
    electionTimeoutMillis: 10000, // デフォルト10秒
    heartbeatTimeoutSecs: 10, // ハートビートタイムアウト
    chainingAllowed: true, // Secondary間チェイニング許可
  },
})

// Replica Set状態確認
rs.status()

// レプリケーション遅延確認
rs.printReplicationInfo()
rs.printSecondaryReplicationInfo()

フェイルオーバーメカニズム

MongoDB Replica Setのフェイルオーバーは、Raftベースの合意アルゴリズムで動作する。Primaryが応答しなくなると、残りのメンバーが選挙を開始し、過半数以上の投票を得たメンバーが新しいPrimaryとして選出される。

選挙条件

  • 過半数(majority)のメンバーが相互通信可能であること
  • priorityが0のメンバーはPrimaryになれない
  • hiddenメンバーやdelayedメンバーは投票のみ可能でPrimary候補から除外される
  • electionTimeoutMillis(デフォルト10秒)以内にPrimaryと通信できない場合、選挙が開始される
// 手動フェイルオーバー実行(メンテナンス時)
rs.stepDown(60) // 60秒間Primary役割を放棄

// 特定メンバーをPrimaryに昇格させるためpriority調整
cfg = rs.conf()
cfg.members[1].priority = 100
rs.reconfig(cfg)

// Hiddenメンバー設定(バックアップ専用)
cfg = rs.conf()
cfg.members[2].hidden = true
cfg.members[2].priority = 0
rs.reconfig(cfg)

// Delayedメンバー設定(データ復旧用、1時間遅延)
cfg = rs.conf()
cfg.members[2].secondaryDelaySecs = 3600
cfg.members[2].hidden = true
cfg.members[2].priority = 0
rs.reconfig(cfg)

Chunkマイグレーションとバランサー

バランサーの動作原理

バランサーはConfig ServerのPrimaryで実行され、シャード間のチャンク数の差が閾値(migration threshold)を超えるとチャンクを移動する。n個のシャードがあるクラスターでは、最大n/2個の同時マイグレーションが可能である。

// バランサー状態確認
sh.getBalancerState()
sh.isBalancerRunning()

// バランサー停止/開始
sh.stopBalancer()
sh.startBalancer()

// バランサー時間ウィンドウ設定(深夜2-6時のみ動作)
db.adminCommand({
  balancerStart: 1,
  condition: {
    activeWindow: { start: '02:00', stop: '06:00' },
  },
})

// 特定コレクションのバランシング無効化
sh.disableBalancing('myapp.orders')

// チャンク手動移動
sh.moveChunk('myapp.orders', { customerId: 'user123' }, 'shard3')

// 現在のマイグレーション状態確認
db.adminCommand({ currentOp: true, desc: /migrate/ })

チャンク管理

// チャンクサイズ変更(デフォルト128MB、MongoDB 7.0+)
use config
db.settings.updateOne(
  { _id: "chunksize" },
  { $set: { value: 64 } },
  { upsert: true }
)

// ジャンボチャンク確認
db.chunks.find({ jumbo: true }).forEach(function(chunk) {
  print("Jumbo chunk: " + chunk.ns + " on " + chunk.shard)
})

// ジャンボチャンク強制分割試行
sh.splitAt("myapp.orders", { customerId: "splitPoint" })

// チャンク分布確認
db.chunks.aggregate([
  { $group: { _id: "$shard", count: { $sum: 1 } } },
  { $sort: { count: -1 } }
])

読み取り/書き込みConcern設定

Read Preferenceモード比較

モード読み取り対象一貫性レイテンシ適合するユースケース
primaryPrimaryのみ強い一貫性基準値リアルタイムデータ必要
primaryPreferredPrimary優先、不可時Secondaryほぼ強い一貫性基準値Primary障害への備え
secondarySecondaryのみ結果整合性低い(最寄りノード)レポート、分析クエリ
secondaryPreferredSecondary優先、不可時Primaryほぼ結果整合性低い読み取り負荷分散
nearestネットワーク遅延最小ノード結果整合性最小地理分散デプロイ

Write Concernレベル比較

Write Concern説明耐久性パフォーマンス適合するユースケース
w: 0応答待ちなし非常に低い最高ログ、メトリクス(損失許容)
w: 1Primary確認のみ普通高い一般的な書き込み
w: "majority"過半数確認高い普通重要データ(推奨デフォルト)
w: NN個ノード確認非常に高い低い金融等の厳格な要件
j: trueジャーナル書き込み確認最高低い耐久性最優先
// 読み取り/書き込みConcern設定例

// 1. コレクションレベルでRead Preference設定
db.orders.find({ status: 'pending' }).readPref('secondaryPreferred')

// 2. 接続文字列での設定
// mongodb://mongos1:27017,mongos2:27017/myapp?readPreference=secondaryPreferred&w=majority

// 3. Write Concern指定
db.orders.insertOne(
  { customerId: 'user123', amount: 50000 },
  { writeConcern: { w: 'majority', j: true, wtimeout: 5000 } }
)

// 4. グローバルデフォルトRead/Write Concern設定
db.adminCommand({
  setDefaultRWConcern: 1,
  defaultWriteConcern: { w: 'majority', j: true },
  defaultReadConcern: { level: 'majority' },
})

モニタリングとパフォーマンスチューニング

重要モニタリングクエリ

// 1. シャード別データ分布確認
db.orders.getShardDistribution()

// 2. 長時間実行中のオペレーション確認
db.currentOp({ secs_running: { $gt: 10 } })

// 3. スロークエリプロファイリング有効化
db.setProfilingLevel(1, { slowms: 100 })

// 4. スロークエリ分析
db.system.profile
  .find({
    millis: { $gt: 100 },
    ns: /myapp\.orders/,
  })
  .sort({ ts: -1 })
  .limit(10)

// 5. サーバー状態確認
db.serverStatus().opcounters // オペレーションカウンター
db.serverStatus().connections // 接続数
db.serverStatus().wiredTiger.cache // キャッシュ状態

// 6. Replica Setレプリケーション遅延確認
db.adminCommand({ replSetGetStatus: 1 }).members.forEach(function (m) {
  if (m.stateStr === 'SECONDARY') {
    var lag = (new Date() - m.optimeDate) / 1000
    print(m.name + ' lag: ' + lag + 's')
  }
})

// 7. インデックス使用統計
db.orders.aggregate([{ $indexStats: {} }])

// 8. Config Serverメタデータ整合性検証(MongoDB 7.0+)
db.adminCommand({ checkMetadataConsistency: 1 })

パフォーマンスチューニングチェックリスト

  • インデックスカバリング:Shard Keyを含む複合インデックスでクエリが単一シャードで処理されるようにする
  • Scatter-Gather最小化:Shard Keyをクエリフィルターに含めて特定シャードのみを対象にする
  • Connection Pooling:mongos接続プールサイズを適切に設定する(デフォルトmaxPoolSize=100)
  • Read Preference活用:分析クエリはSecondaryに分散する
  • チャンクサイズ調整:書き込みが多いワークロードはチャンクサイズを縮小してマイグレーション影響を最小化する
  • WiredTigerキャッシュ:cacheSizeGBをシステムRAMの50-60%に設定する

バックアップとリカバリ

mongodump/mongorestoreによるバックアップ

# 1. バックアップ前にバランサー停止(シャードクラスター)
mongosh --host mongos1:27017 --eval "sh.stopBalancer()"

# 2. 個別シャードバックアップ(--oplogでポイントインタイム一貫性確保)
mongodump --host shard1/mongo-shard1-a:27018 \
  --oplog --out /backup/shard1-$(date +%Y%m%d)

mongodump --host shard2/mongo-shard2-a:27018 \
  --oplog --out /backup/shard2-$(date +%Y%m%d)

# 3. Config Serverバックアップ
mongodump --host configRS/config1:27019 \
  --oplog --out /backup/config-$(date +%Y%m%d)

# 4. バランサー再開
mongosh --host mongos1:27017 --eval "sh.startBalancer()"

# 5. リストア(--oplogReplayでポイントインタイム復旧)
mongorestore --host shard1/mongo-shard1-a:27018 \
  --oplogReplay /backup/shard1-20260313

ポイントインタイムリカバリ(PITR)とOplog

// Oplogサイズと保持時間確認
db.getReplicationInfo()
// 出力例:
// configured oplog size:   2048MB
// log length start to end: 172800secs (48hrs)
// oplog first event time:  ...
// oplog last event time:   ...

// Oplogサイズ変更(最低48時間以上の保持を推奨)
db.adminCommand({ replSetResizeOplog: 1, size: 4096 }) // 4GB

プロダクションバックアップ推奨事項

  • 小規模クラスターではmongodumpで十分だが、大規模環境ではファイルシステムスナップショット(LVM/EBS)またはMongoDB Atlas Backupの使用を推奨
  • Oplogサイズは最低48時間以上の書き込みを保持できるよう設定する
  • Delayed Secondaryメンバーを活用すると、論理的エラー(誤ってデータ削除など)から迅速に復旧できる
  • バックアップリストアテストを定期的に実施する(最低月1回)

トラブルシューティング事例

1. Shard Keyホットスポット

症状:特定シャードのCPU/ディスクI/Oのみ高く、残りのシャードはアイドル状態

原因:単調増加フィールド(ObjectId、timestamp)をRanged Shard Keyとして使用し、全ての新規書き込みが最後のチャンクに集中

解決策

  • Hashed Shard Keyでreshardingを実行(MongoDB 5.0+)
  • Compound Shard Keyを使用してカーディナリティを向上
  • MongoDB 8.0の sh.shardAndDistributeCollection() で高速再分配

2. Split-brain(ネットワークパーティション)

症状:2つ以上のノードが同時にPrimaryとして動作し、データ不整合が発生する可能性

原因:ネットワークパーティションによりReplica Setが2つのグループに分離し、各グループが別々にPrimaryを選出

解決策

  • Replica Setメンバーを奇数(3、5、7)に維持し、常に過半数グループが1つだけ存在するよう構成
  • ネットワークパーティション時、過半数に属さないPrimaryは自動的にSecondaryに降格される
  • w: "majority" Write Concernを使用して、過半数に複製されない書き込みを防止

3. Chunkマイグレーション失敗

症状:バランサーログに「Data transfer error」または「WriteConcernFailed」エラー

原因:レプリケーション遅延が深刻、ネットワーク帯域不足、または対象シャードのディスク容量不足

解決策

// _secondaryThrottle有効化でレプリケーション同期を保証
use config
db.settings.updateOne(
  { _id: "balancer" },
  { $set: { "_secondaryThrottle": { w: 2 } } },
  { upsert: true }
)

// rangeDeleterバッチサイズ縮小
db.adminCommand({
  setParameter: 1,
  rangeDeleterBatchSize: 32,
  rangeDeleterBatchDelayMS: 100
})

4. Replica Lag(レプリケーション遅延)

症状:SecondaryのoptimeDateがPrimaryより数十秒以上遅れている

原因:大量書き込み負荷、遅いディスクI/O、インデックスビルド、ネットワークボトルネック

解決策

  • WiredTigerキャッシュサイズを確認し、必要に応じて増加
  • Secondaryで実行中の重いクエリを確認して中断
  • ネットワーク帯域を確認しOplog転送経路を最適化
  • インデックスビルドはローリング方式(1ノードずつ順次)で実行

運用チェックリスト

デプロイ前

  • Shard Keyをクエリパターン、カーディナリティ、書き込み分布に基づいて選択したか
  • 各Replica Setが最低3メンバー(奇数)で構成されているか
  • Config Serverが別途Replica Set(3ノード)として構成されているか
  • mongosが2台以上でロードバランサーの背後にあるか
  • 認証(keyFileまたはx.509)とネットワーク暗号化(TLS)が設定されているか

日常運用

  • バランサーが正常動作しチャンクが均等分布されているか
  • Replica Lagが閾値(例:10秒)以内か
  • 接続数がmaxIncomingConnections上限に対して適切か
  • スロークエリログを定期的に分析しているか
  • ディスク使用率が70%以下か

バックアップ/リカバリ

  • 自動バックアップが日次で実行されているか
  • Oplogサイズが最低48時間以上のデータを保持しているか
  • バックアップリストアテストを直近30日以内に実施したか
  • Delayed Secondaryが構成されて論理的エラーに備えているか

障害対策

  • 自動フェイルオーバーテストを直近四半期内に実施したか
  • モニタリングアラートがReplica Lag、ディスク使用率、接続数に対して設定されているか
  • Split-brainシナリオに対する復旧手順が文書化されているか

参考資料