MongoDB: シャーディング:水平スケーリング

最終更新:2026-08-26

シャードクラスターはMongoDB水平スケーリングの究極のソリューション—習得すればペタバイト規模のデータをサポートできます。

1. 学習内容



2. シャーディングアーキテクチャ

概念説明: シャーディングはMongoDBの水平スケーリングソリューション—データを複数のシャードに分散し、各シャードは独立したレプリカセットです。アプリケーションはMongosルーティングを通じて透過的にデータにアクセスし、データ分散を意識する必要がありません。単一サーバーが容量または書き込みスループットの限界に達した場合、シャーディングが唯一のスケール方法です。

動作原理: Mongosはクエリルーターとして機能します。アプリケーションからリクエストを受信すると、Config Serverからシャードメタデータ(どのチャンクがどのシャードにあるか)を取得し、ターゲットシャードにリクエストをルーティングして実行し、結果をマージして返します。アプリケーションコードは単一ノードMongoDBと同一—シャーディングはアプリケーションに対して透過的です。

3つの主要コンポーネント:

100%
graph TB
    App[アプリケーション] --> M[Mongos<br/>クエリルーター<br/>ステートレス]
    M --> CS[Config Server<br/>設定情報<br/>3ノードレプリカセット]
    M --> S1[Shard 1<br/>シャードノード1<br/>レプリカセット]
    M --> S2[Shard 2<br/>シャードノード2<br/>レプリカセット]
    M --> S3[Shard 3<br/>シャードノード3<br/>レプリカセット]

    subgraph "データフロー"
        Q[クエリリクエスト] --> M
        M -->|メタデータ照会| CS
        M -->|データ照会| S1
        M -->|データ照会| S2
        M -->|データ照会| S3
    end

    style M fill:#cce5ff
    style CS fill:#fff3cd
    style S1 fill:#d4edda
    style S2 fill:#d4edda
    style S3 fill:#d4edda
コンポーネント 責務 デプロイ要件
Mongos クエリルーティング、アプリケーション透過 ステートレス、複数インスタンス対応
Config Server シャードメタデータ保存(クラスター設定) レプリカセット必須(3ノード)
Shard 実際のデータ保存(各シャードはレプリカセット) 最低3ノードのレプリカセット

シャード vs レプリカセット:

項目 レプリカセット シャードクラスター
目的 高可用性(HA) 水平スケーリング + HA
データ 各ノードが全データセットを保存 データは複数シャードに分散
書き込みスケール なし(全書き込みはPrimaryへ) ✅ 複数シャードに分散
読み取りスケール Secondary読み取り負荷分散 ✅ マルチシャード並列クエリ
運用複雑さ 中程度 高い
適用規模 < 1 TB > 1 TB / > 10K ops/s


3. シャードキー選択戦略

概念説明: シャードキーはデータがシャード間でどう分散されるかを決定するフィールドです—クエリパフォーマンス、データバランス、スケーラビリティに直接影響します。一度設定するとシャードキーは変更不可で、誤ったシャードキー選択は最も深刻なアーキテクチャミスの一つです。

動作原理: MongoDBはシャードキーに基づいてデータをチャンク(デフォルト64 MB)に分割し、各チャンクを特定のシャードに割り当てます。範囲シャーディングはシャードキー値の範囲でデータを分割し、ハッシュシャーディングはシャードキーのハッシュ値でデータを分割します。クエリ時、Mongosはシャードキーを使用してターゲットチャンクを含むシャードを特定し、ブロードキャストクエリを回避します。

シャードキー選択のESRTルール:

順序 タイプ 説明
Equality(等価) 等価フィルタリング userId、_idを優先 find({userId: 'u001'})
Sort(ソート) ソートフィールド createdAtなど sort({createdAt: -1})
Range(範囲) 範囲クエリ 頻繁な範囲選択 {price: {$gte: 100}}
Time(時系列) 時系列データ 作成日時など {createdAt: 1}

良いパーティションキーの4つの特性:

特性 説明 理由
高カーディナリティ 多数の一意値 より細かいチャンクに分割可能
低頻度更新 値がほとんど変更されない シャードキー変更はチャンク移行を伴う
クエリヒット クエリ条件にシャードキーを含む Mongosはターゲットルーティング可能
均等分散 値が均等に分布 ホットスポットシャードを回避

(1) 範囲シャーディング(Range Partitioning)

概念説明: 範囲シャーディングはシャードキー値の自然順序に基づいてチャンクを分割—連続する値の範囲が同じチャンクに配置されます。範囲クエリやソートに適していますが、データ偏在(ホットスポット)を引き起こしやすいです。

原理: 範囲シャーディングはシャードキーの値範囲を連続する区間[min, splitPoint1), [splitPoint1, splitPoint2), ...に分割し、各区間がチャンクに対応します。隣接する値を持つドキュメントは同じチャンク → 同じシャードに配置されます。

JAVASCRIPT
// === シャーディング有効化 ===
sh.enableSharding('shopdb');

// === シャードキー選択:ユーザーID範囲 ===
sh.shardCollection('shopdb.orders', { userId: 1 });
// userId 1-1000 → Shard 1
// userId 1001-2000 → Shard 2
項目 範囲シャード 説明
範囲クエリ ✅ 高効率 連続データが同一シャードに
ソート ✅ 高効率 インデックスは自然順序
データ分散 ⚠️ 偏在の可能性 ホットキーが単一シャードに集中
適用シーン 時系列、ID範囲 createdAt、userId

(2) ハッシュシャーディング

概念説明: ハッシュシャーディングはシャードキーのハッシュを計算し、ハッシュ値範囲でチャンクを分割します。データは均等に分散されホットスポットはありませんが、範囲クエリは全シャードへのブロードキャストが必要です。

JAVASCRIPT
// === ハッシュシャーディング ===
sh.shardCollection('shopdb.products', { sku: 'hashed' });
// skuのハッシュ値で均等にシャードに分散
項目 ハッシュシャード 説明
データ分散 ✅ 均等 ハッシュ関数で自然に散らばる
書き込み分散 ✅ 均等 ホットスポットなし
範囲クエリ ❌ ブロードキャスト必要 隣接値は異なるシャードに
適用シーン 主に等価クエリ SKU、email、userID

(3) 範囲 vs ハッシュ比較

項目 範囲 ハッシュ
データ分散 偏在の可能性 均等
範囲クエリ ✅ ターゲットルート ❌ 全シャードにブロードキャスト
等価クエリ
ソート ✅ 順序付きインデックス
書き込みホットスポット ⚠️ 単一ポイントホットスポット ✅ 均等
複合シャードキー ✅ 対応 ❌ 単一フィールドのみ

(4) 最適なシャードキー選択

JAVASCRIPT
// ✅ 良いシャードキー:高カーディナリティ、低頻度更新、クエリヒット
sh.shardCollection('shopdb.orders', { userId: 1, createdAt: -1 });
// 複合シャードキー、userId範囲 + 時系列ソート

// ❌ 不適切なシャードキー:低カーディナリティ
sh.shardCollection('shopdb.products', { category: 1 });
// categoryは5つの値しかなく、深刻な偏在を引き起こす

パーティションキーのアンチパターン:

アンチパターン 結果 ベストプラクティス
低カーディナリティフィールド(categoryなど) 深刻なデータ偏在 高カーディナリティフィールドを選択(userId)
単調増加フィールド(ObjectIdなど) 新規データが同じシャードに書き込まれる ハッシュまたは複合シャードキーを使用
頻繁に更新されるフィールド 頻繁なチャンク移行 低頻度更新フィールドを選択
クエリ条件に含まれない 全クエリがブロードキャスト 高頻度クエリフィールドを選択


4. チャンク分割と移行

概念説明: チャンクはシャーディングデータ管理の最小単位—デフォルト64 MB—で、特定の範囲のシャードキーを持つ全ドキュメントを含みます。チャンクは閾値を超えると自動的に分割され、シャード間でチャンク数が不均等になると自動的に移行されます。これがMongoDB自動負荷分散の中核メカニズムです。

動作原理:

バランサーの動作:

100%
graph TB
    subgraph "バランサーワークフロー"
        A[各シャードの<br/>チャンク数をチェック] --> B{差が>8?}
        B -->|はい| C[移行元シャードを選択<br/>(最も多いチャンク)]
        C --> D[移行先シャードを選択<br/>(最も少ないチャンク)]
        D --> E[チャンク移行]
        E --> F[Config Server<br/>メタデータ更新]
        F --> A
        B -->|いいえ| G[次回チェック待機<br/>デフォルト10秒]
    end

    style E fill:#cce5ff
    style F fill:#d4edda
100%
graph LR
    A[Chunk 1<br/>1-1000] -->|分割| B[Chunk 1a<br/>1-500]
    A -->|分割| C[Chunk 1b<br/>501-1000]
    C -->|移行| D[Shard 2]

    style B fill:#d4edda
    style D fill:#fff3cd

チャンク操作コマンド:

操作 コマンド 説明
手動分割 sh.splitAt(ns, key) 指定キー値で分割
分散確認 db.col.getShardDistribution() シャード別データ量
クラスター状態 sh.status() チャンク分散詳細
バランサー状態 sh.isBalancerRunning() バランシング実行中か
バランサー開始/停止 sh.startBalancer() / sh.stopBalancer() メンテナンス時間中に一時停止可能
チャンクサイズ変更 db.settings.save({_id:'chunksize', value: 128}) 単位: MB
JAVASCRIPT
// === 手動でチャンクを分割 ===
sh.splitAt('shopdb.orders', { userId: 5000 });

// === チャンク分散を確認 ===
db.orders.getShardDistribution();

// === バランサー状態 ===
sh.status();
sh.isBalancerRunning();

ポイント解説:

  1. チャンク分割は論理操作(メタデータのみ変更)でデータは移動しません。チャンク移行は物理操作(実際にデータをコピー)。
  2. バランサーはバックグラウンドで実行され、移行中も読み書き操作に影響しません(二重書き込みで整合性を保証)。
  3. メンテナンス時間中(バッチインポートなど)は、インポートパフォーマンスへの影響を防ぐためバランサーを一時停止を推奨。

▶ サンプル 1:バランサー管理と手動チャンク分割

JAVASCRIPT
// ShopHub:大規模インポート前にバランサーを一時停止、インポート後に復旧
// 1. バランサー一時停止
sh.stopBalancer();

// 2. バッチインポートデータ
for (let i = 0; i < 1000000; i++) {
  db.orders.insertOne({ userId: 'user_' + (i % 500), total: Math.random() * 1000, createdAt: new Date() });
}

// 3. ホットスポットチャンクを手動分割
sh.splitAt('shopdb.orders', { userId: 'user_100' });
sh.splitAt('shopdb.orders', { userId: 'user_200' });
sh.splitAt('shopdb.orders', { userId: 'user_300' });

// 4. バランサー復旧
sh.startBalancer();

// 5. 分散を確認
db.orders.getShardDistribution();

出力:

TEXT 📖 参照専用
Shard shard1 at shard1/host1:27018
  data: 500MB docs: 500000 chunks: 50
Shard shard2 at shard2/host2:27018
  data: 500MB docs: 500000 chunks: 50


5. Dockerシャードクラスター構築

概念概要: シャードクラスターのデプロイはレプリカセットより複雑—Config Serverレプリカセット、複数のシャードレプリカセット、Mongosルーティングが必要です。Docker Composeでこれら全コンポーネントを一発でオーケストレーションでき、開発・テスト環境に最適です。

デプロイアーキテクチャ:

100%
graph TB
    subgraph "Config Serverレプリカセット"
        CS1[config1:27019]
    end

    subgraph "Shard 1レプリカセット"
        S1A[shard1a:27018]
    end

    subgraph "Shard 2レプリカセット"
        S2A[shard2a:27020]
    end

    subgraph "Mongosルーティング"
        MS[mongos:27017]
    end

    App[アプリケーション] --> MS
    MS --> CS1
    MS --> S1A
    MS --> S2A

    style MS fill:#cce5ff
    style CS1 fill:#fff3cd
    style S1A fill:#d4edda
    style S2A fill:#d4edda

本番 vs 開発構成の比較:

コンポーネント 本番環境 開発環境
Config Server 3ノードレプリカセット 1ノード(テストのみ)
各シャード 3ノードレプリカセット 1ノード
Mongos 複数インスタンス(負荷分散) 1インスタンス
合計mongod数 3 + 3 × 3 = 12+ 1 + 2 + 1 = 4
YAML
# docker-compose-sharding.yml
version: '3.8'
services:
  # Config Server(レプリカセット)
  config1:
    image: mongo:7.0
    command: mongod --configsvr --replSet configReplSet --port 27019

  # Shard 1(レプリカセット)
  shard1a:
    image: mongo:7.0
    command: mongod --shardsvr --replSet shard1ReplSet --port 27018

  # Mongosルーティング
  mongos:
    image: mongo:7.0
    command: mongos --configdb configReplSet/config1:27019 --port 27017
    ports:
      - "27017:27017"

初期化ステップ:

ステップ コマンド 説明
1 config1でrs.initiate() Config Serverレプリカセット初期化
2 shard1aでrs.initiate() Shard 1レプリカセット初期化
3 mongosでsh.addShard() クラスターにシャード追加
4 sh.enableSharding() データベースシャーディング有効化
5 sh.shardCollection() シャードキーを選択
BASH
# === シャードクラスター初期化 ===
# 1. Config Serverレプリカセット初期化
mongosh --port 27019 --eval 'rs.initiate({_id: "configReplSet", members: [{_id: 0, host: "config1:27019"}]})'

# 2. Shard 1レプリカセット初期化
mongosh --port 27018 --eval 'rs.initiate({_id: "shard1ReplSet", members: [{_id: 0, host: "shard1a:27018"}]})'

# 3. Mongos経由でシャードを追加
mongosh --port 27017 --eval '
  sh.addShard("shard1ReplSet/shard1a:27018");
  sh.enableSharding("shopdb");
  sh.shardCollection("shopdb.orders", { userId: 1 });
'


6. シャードクラスター監視

概念説明: シャードクラスターの監視は単一ノードやレプリカセットより複雑—各コンポーネントの健全性、データ分散バランス、チャンク移行状態などを監視する必要があります。適切な監視でデータ偏在、ホットシャード、設定問題を早期に発見できます。

監視項目:

項目 コマンド 主要メトリクス
クラスター概要 sh.status() シャード数、チャンク分散、バランサー状態
データ分散 db.col.getShardDistribution() シャード間データ量がバランスしているか
バランサー sh.isBalancerRunning() 移行状態、移行進捗
設定情報 db.settings.find() チャンクサイズ、バランサー設定
シャード健全性 sh.status().shards シャード到達可能性
JAVASCRIPT
// === シャード状態確認 ===
sh.status();

// === データ分散確認 ===
db.orders.getShardDistribution();

// === バランサー管理 ===
sh.startBalancer();
sh.stopBalancer();
sh.setBalancerState(true);

// === 設定確認 ===
db.settings.find();

よくある問題のトラブルシューティング:

問題 症状 診断 解決策
データ偏在 特定シャードのデータ量が他より大幅に多い getShardDistribution() ホットチャンクを手動分割
ジャンボチャンク 64MB超のチャンクが分割できない sh.status()でjumbo表示 chunkSizeを増やすかキーを分割
バランサー停止 移行が進まない sh.isBalancerRunning() Config Server健全性を確認
クエリブロードキャスト 全シャードがクエリされた explain()でSHARD_MERGE表示 クエリ条件にシャードキーを含める

▶ サンプル 2:シャード監視とトラブルシューティング

JAVASCRIPT
// BobのDataFlowシステムでクエリが遅い、シャーディング問題を診断
// 1. クラスター状態確認
sh.status();
// Shard1: 8000 chunks, Shard2: 2000 chunks → 深刻な偏在を発見

// 2. データ分散確認
db.orders.getShardDistribution();
// Shard 1: 80GB (80%), Shard 2: 20GB (20%)

// 3. ジャンボチャンクがあるか確認
db.config.chunks.find({ jumbo: true }).count();
// 3つのJumbo Chunk

// 4. ホットスポットチャンクを手動分割
sh.splitAt('shopdb.orders', { userId: 'user_100' });
sh.splitAt('shopdb.orders', { userId: 'user_200' });

// 5. バランサーが実行中か確認
sh.startBalancer();
sh.isBalancerRunning(); // true

// 6. バランシング完了を待ち、再確認
db.orders.getShardDistribution();
// Shard 1: 50GB (50%), Shard 2: 50GB (50%) → バランス


7. いつシャーディングを使うべきか?

概念説明: シャーディングは早すぎても遅すぎてもいけません—スケーラビリティを提供しますが、運用複雑さも増します。早すぎるシャーディングは不要な運用コストを生み、遅すぎるシャーディングは単一サーバーのボトルネックとデータ移行の困難を招きます。データ量、書き込みスループット、成長傾向を総合的に評価して決定すべきです。

意思決定フレームワーク:

100%
graph TB
    A{データ量?} -->|< 100GB| B[❌ 単一サーバー + レプリカセット]
    A -->|100GB-1TB| C{書き込みQPS?}
    A -->|> 1TB| D[✅ シャーディング]
    C -->|< 10K ops/s| E[⚠️ レプリカセット<br/>成長傾向を監視]
    C -->|> 10K ops/s| D

    B --> F[インデックス最適化 + 検索]
    E --> G[シャードキー事前計画]
    D --> H[パーティションキー選択 + シャードクラスター構築]

    style B fill:#d4edda
    style D fill:#cce5ff
    style E fill:#fff3cd
シーン シャーディング 理由
データ量 < 100 GB ❌ 単一サーバーで十分 シャーディングはメリットなしで複雑さが増す
データ量: 100 GB – 1 TB ⚠️ 状況による 書き込みQPSと成長率を評価
データ量 > 1 TB ✅ 推奨 単一サーバーの容量・I/Oボトルネック
書き込み > 10K ops/s ✅ 推奨 単一Primary書き込みボトルネック
継続的なデータ量増加 ✅ 推奨 事前に計画し、強制シャーディングを回避

シャーディング前のチェックリスト:

準備項目 説明
インデックス最適化が限界か確認 まずexplain()でクエリ問題を除外
垂直スケーリングが限界か確認 CPU、メモリ、SSDアップグレードで十分か?
適切なパーティションキーを選択 高カーディナリティ、低更新頻度、クエリヒット
レプリカセットをデプロイ シャーディングの基盤、各シャードはレプリカセット
容量を計画 データ成長を見積もり、シャード数を決定
アプリケーション互換性テスト クエリにシャードキーを含む、ユニークインデックスにシャードキーを含む

▶ サンプル:Dockerでシャードクラスター構築 + データシャーディングのハンズオンガイド

BASH
# === 1. シャードクラスター起動(docker-compose-sharding.yml)===
# services:
#   config1:
#     image: mongo:7.0
#     command: mongod --configsvr --replSet configReplSet --bind_ip_all --port 27019
#     ports: ["27019:27019"]
#
#   shard1a:
#     image: mongo:7.0
#     command: mongod --shardsvr --replSet shard1ReplSet --bind_ip_all --port 27018
#     ports: ["27018:27018"]
#
#   shard2a:
#     image: mongo:7.0
#     command: mongod --shardsvr --replSet shard2ReplSet --bind_ip_all --port 27020
#     ports: ["27020:27020"]
#
#   mongos:
#     image: mongo:7.0
#     command: mongos --configdb configReplSet/config1:27019 --bind_ip_all --port 27017
#     ports: ["27017:27017"]
#     depends_on: [config1, shard1a, shard2a]

docker-compose -f docker-compose-sharding.yml up -d

# === 2. Config Serverレプリカセット初期化 ===
mongosh --port 27019 --eval '
  rs.initiate({
    _id: "configReplSet",
    members: [{ _id: 0, host: "config1:27019" }]
  });
'

# === 3. シャードレプリカセット初期化 ===
mongosh --port 27018 --eval '
  rs.initiate({ _id: "shard1ReplSet", members: [{ _id: 0, host: "shard1a:27018" }] });
'

mongosh --port 27020 --eval '
  rs.initiate({ _id: "shard2ReplSet", members: [{ _id: 0, host: "shard2a:27020" }] });
'

# === 4. Mongos経由でシャードを追加 ===
mongosh --port 27017 --eval '
  sh.addShard("shard1ReplSet/shard1a:27018");
  sh.addShard("shard2ReplSet/shard2a:27020");
'

# === 5. データベースシャーディング有効化 ===
mongosh --port 27017 --eval '
  sh.enableSharding("shopdb");
  sh.shardCollection("shopdb.orders", { userId: 1, createdAt: -1 });  // 複合シャードキー
'

# === 6. テストデータ挿入(自動的に異なるシャードに分散)===
mongosh --port 27017 --eval '
  for (let i = 0; i < 10000; i++) {
    db.orders.insertOne({
      userId: "user_" + (i % 100),
      total: Math.random() * 1000,
      createdAt: new Date(),
      status: "paid"
    });
  }
'

# === 7. シャード分散確認 ===
mongosh --port 27017 --eval 'sh.status();'
# 出力:
# shards:
#   { _id: 'shard1ReplSet', count: 5012 }
#   { _id: 'shard2ReplSet', count: 4988 }
# データは2つのシャードに均等に分散

# === 8. Mongosクエリで自動ルーティング ===
mongosh --port 27017 --eval '
  db.orders.find({ userId: "user_50" }).count();
'
# Mongosは自動的に対応シャードにルーティング(userId範囲に基づく)
JAVASCRIPT
// === 9. アプリケーションからMongosに接続(アプリ透過、シャーディング透過)===
const mongoose = require('mongoose');
await mongoose.connect('mongodb://localhost:27017/shopdb');
// アプリケーションコードの変更なし、スタンドアロンMongoDBと全く同じ

await Order.create({
  userId: 'user_001',
  total: 599,
  items: [{ sku: 'PHONE-001', qty: 1 }]
});
// Mongosは自動的に適切なシャードにルーティング

// === 10. ハッシュシャーディング(データを均等分散)===
mongosh --port 27017 --eval '
  sh.shardCollection("shopdb.products", { sku: "hashed" });
'
// skuはハッシュ後均等に分散、ホットスポットを回避

出力:

TEXT 📖 参照専用
10,000件の注文が2つのシャードに自動分散(5,012 + 4,988)。アプリケーションはMongos経由で接続し、シャーディング詳細を意識する必要なし。

❓ よくある質問

Q シャーディング後もユニークインデックスを使用できますか?
A はい、ただしユニークインデックスにはシャードキーを含む必要があります(複合ユニークインデックス)。
Q シャードキーは変更できますか?
A いいえ。シャードキーは一度設定すると変更できません(コレクション再作成が必要)。
Q シャードは多ければ多いほど良いですか?
A いいえ。シャードが多すぎると運用複雑さが増します。3–12シャードを推奨。
Q 本番はAtlasを使うべきか、自前でシャード構築すべきか?
A Atlasを推奨(マネージド + 自動化)。自前シャード構築は専任DBAチームを持つ大企業のみ推奨。

📖 まとめ


📝 練習問題

  1. 基礎問題(⭐): シャーディングアーキテクチャとコンポーネント(Mongos / Config Server / Shard)を理解する。
  2. 基礎問題(⭐): ビジネスシナリオを分析し、シャーディングが必要か判断する。
  3. 応用問題(⭐⭐): Docker ComposeでConfig 1つ + Shard 2つのクラスターを構築。
  4. 応用問題(⭐⭐): シャードキー選択をテスト(userId vs. sku vs. 複合)。
  5. チャレンジ(⭐⭐⭐): 完全なシャードクラスターをデプロイ(Configレプリカセット + 2シャードレプリカセット + Mongos + 監視)。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%