MongoDBのディスク容量管理と削減のベストプラクティス

この包括的なベストプラクティスガイドでMongoDBのディスク使用量を最適化しましょう。コレクションとインデックスの圧縮、不要なインデックスの特定と削除、WiredTigerの圧縮機能の活用に関する効果的な戦略を学びます。データアーカイブの実装、oplogサイズの管理、ディスク容量のプロアクティブな監視方法を発見し、システム障害を防止してパフォーマンスを向上させましょう。この記事では、MongoDBのデプロイメントを効率的でスリムに保つための実用的な洞察と実例を提供します。

MongoDBのディスク容量管理と削減のベストプラクティス

MongoDBのディスク使用量が緊急の問題になるのは、たいてい最悪のタイミングです。バッチジョブが予想以上に長く実行されたり、削除しても容量が解放されないように見えたり、レプリカセットのメンバーがボリュームの空き容量がほぼなくなったと警告を出し始めたりします。修正方法は、単一の魔法のようなコマンドで済むことはほとんどありません。容量がライブデータ、インデックス、再利用可能なWiredTiger領域、oplog、ログ、ローカルバックアップのいずれに使用されているかを把握する必要があります。

最も安全なアプローチは、まず測定し、不要なものを削減し、その後にのみ圧縮やメンバーの再構築などのより負荷の高いメンテナンスを実行することです。この順序により、ほとんど容量を戻せない長期間のメンテナンスイベントを作成することを防げます。

MongoDBのディスク容量消費の理解

MongoDBは、以下のいくつかのコンポーネントにディスク容量を使用します。

  • データファイル: コレクション内の実際のBSONドキュメントを保存します。
  • インデックスファイル: 効率的なクエリ実行をサポートするために作成されたB-treeインデックスを保存します。
  • ジャーナルファイル (WiredTiger): データファイルに適用される前に書き込み操作を記録し、データの永続性を保証します。これらは事前に割り当てられます。
  • Oplog (操作ログ): レプリカセット内の特別なcappedコレクションで、すべての書き込み操作を記録します。レプリケーションに不可欠です。
  • 診断データ: ログ、mongodプロセスファイル、その他のシステム関連情報。

時間の経過とともに、更新、削除、ドキュメントの成長(パディング)により、コレクションとインデックスは断片化したり、未使用の割り当て済み領域を含んだりする可能性があり、非効率的なディスク使用につながります。この「ホワイトスペース」は、データベースがライブデータに必要としなくなった場合でも、オペレーティングシステムによってすぐには回収されません。

MongoDBのディスク容量を削減するための戦略

1. コレクションとインデックスの圧縮

圧縮操作は、データファイルとインデックスファイルをより効率的に書き換えることで、未使用のディスク容量を再利用するのに役立ちます。これは、大量のデータ削除や更新の後に特に有効です。

コレクションの圧縮

WiredTigerストレージエンジン(MongoDB 3.2以降のデフォルト)では、compactは主に削除されたドキュメントから空き領域を再利用し、コレクションのデフラグを行います。MMAPv1のcompact操作のように、コレクションのデータファイルを最初から再構築するわけではありません。

db.runCommand({ compact: "myCollection" })

compactに関する考慮事項:

  • compact操作はリソースを大量に消費し(CPU、I/O)、特に大規模なコレクションではかなりの時間がかかる可能性があります。メンテナンスウィンドウ中やレプリカセットのセカンダリメンバーで実行するのが最適です。
  • ディスク要件とロック動作は、MongoDBのバージョン、ストレージエンジン、デプロイメントの形状によって異なります。大規模な本番コレクションで実行する前に、正確なバージョンのドキュメントを確認してください。
  • シャーディングされたクラスターの場合、各シャードで個別にcompactを実行します。

インデックスの再構築

インデックスも断片化する可能性があります。インデックスを再構築すると、容量を再利用し、クエリのパフォーマンスが向上する可能性があります。

db.myCollection.reIndex()

reIndex()に関する考慮事項:

  • reIndex()の動作はMongoDBのバージョンによって変更されており、ビジーなシステムでは依然として混乱を招く可能性があります。バージョンのマニュアルを確認し、ステージングでテストし、可能な場合はレプリカセットメンバーを通じてロールリング作業を優先してください。
  • compactと同様に、reIndex()は操作中に追加のディスク容量を必要とします。

repairDatabase (オフライン操作)

深刻な断片化やデータ破損の場合、repairDatabaseはすべてのデータファイルを再構築できます。これはオフライン操作であり、mongodインスタンスを停止する必要があります。

mongod --repair

警告: repairDatabaseは、注意深く扱わなければ破壊的な操作であり、非常に長い時間がかかる可能性があるため、容量再利用のための最後の手段として使用する必要があります。常にバックアップを用意してください。

2. インデックスの最適化

インデックスはパフォーマンスに不可欠ですが、かなりのディスク容量を消費する可能性があります。未使用または冗長なインデックスは、純粋なオーバーヘッドです。

不要なインデックスの特定と削除

インデックスがまだ必要かどうかを定期的に確認してください。

  1. コレクションのすべてのインデックスを一覧表示:
    
    

db.myCollection.getIndexes() ``` 2. インデックスの使用状況を監視: $indexStats、クエリプラン、プロファイリング、アプリケーションのワークロード履歴を使用します。コレクションの統計はインデックスサイズを示しますが、インデックスが有用であることを証明するわけではありません。 3. 重複または冗長なインデックスを特定: たとえば、{ a: 1, b: 1 }のインデックスは、複合インデックスを使用できるクエリに対して{ a: 1 }のインデックスを冗長にします。{ a: 1, b: 1 }のインデックスは、abのみを含むクエリに対して、{ a: 1, b: 1, c: 1 }のインデックスでもカバーされます。

特定したら、未使用のインデックスを削除します。

db.myCollection.dropIndex("indexName")

ヒント: 本番環境に適用する前に、ステージング環境でインデックスを削除した場合の影響を必ずテストしてください。

部分インデックスの使用

部分インデックスは、指定されたフィルター式を満たすコレクション内のドキュメントのみをインデックス化します。これにより、インデックス化されるドキュメントの数が減り、ディスク容量を節約し、書き込みパフォーマンスが向上します。

db.orders.createIndex(
   { customerId: 1, orderDate: -1 },
   { partialFilterExpression: { status: "active" } }
)

このインデックスは、statusが"active"のドキュメントのみを含むため、ほとんどの注文が履歴、キャンセル済み、アーカイブ済み、またはホットパス外にある場合、そのサイズが削減されます。重要なのは「active」という単語ではなく、アプリケーションが実際に毎日クエリするサブセットをインデックス化する習慣です。

クリーンアップコマンドではなく、ディスク容量のトリアージから始める

MongoDBのディスク容量が増加している場合、最初の間違いはcompactrepair、または古いデータの削除にすぐに飛びつくことです。これらのアクションは役立つ場合もありますが、負荷を生み出したり、状況によってはロックを取得したり、数週間実際の問題を隠したりする可能性もあります。まずは3つの質問に答えることから始めてください。

  • どのファイルシステムが満杯になっているか:データベースパス、ジャーナルパス、ログパス、バックアップボリューム?
  • ライブデータが増加しているのか、それとも削除や更新後に割り当て済みだが未使用の領域が増加しているのか?
  • 増加はコレクション、インデックス、oplog、ログ、診断データ、スナップショットのいずれから来ているのか?

簡単な最初のパスは通常、次のようになります。

df -h
du -h --max-depth=1 /var/lib/mongodb | sort -h
du -h --max-depth=1 /var/log/mongodb | sort -h

次に、シェル内からMongoDBを確認します。

db.adminCommand({ listDatabases: 1 })
db.getSiblingDB("app").stats()
db.getSiblingDB("app").orders.stats()

storageSizetotalIndexSizedataSizeはそれぞれ異なる意味を持ちます。dataSizeが増加している場合、おそらくデータライフサイクルの問題があります。storageSizedataSizeよりもはるかに大きい場合、削除後の再利用可能な内部領域を見ている可能性があります。totalIndexSizedataSizeと比較して大きい場合、圧縮に触れる前にインデックス設計に注意を払う価値があります。

MongoDBが何を戻せて何を戻せないかを理解する

WiredTigerでは、ドキュメントを削除すると、通常はMongoDBが再利用できる領域が解放されます。ただし、それが常にすぐにオペレーティングシステムに返されるわけではありません。この動作は、緊急クリーンアップ中に人々を驚かせます。大量のバッチを削除し、df -hを実行しても、ほとんど改善が見られないのです。

これは削除が失敗したことを意味するわけではありません。MongoDBは、将来の挿入や更新のためにその領域を再利用できることが多いことを意味します。目標が成長を止めることである場合、古いデータを削除またはアーカイブするだけで十分かもしれません。目標がボリュームの空き容量がほとんどない、またはホストのサイズを縮小しているためにファイルシステムを縮小することである場合、圧縮、レプリカセットメンバーの再同期、またはダンプアンドリストア形式の再構築が必要になる場合があります。

本番システムでは、通常、作業を2つのトラックに分けます。最初のトラックは即時の安全性です。ディスクの追加、明らかなログの蓄積の削除、リスクの高いバッチジョブの一時停止、またはデータベースボリュームからのバックアップの移動です。2番目のトラックは実際の削減です。保持期間の修正、未使用のインデックスの削除、そしてバイトがどこに行ったかを把握した後にのみストレージを再構築します。

デフラグの前にデータ保持期間を修正する

アプリケーションがリクエストログ、イベント、セッション、通知、ジョブレコード、または分析ドキュメントを永久に保持する場合、どれだけ注意深く圧縮してもディスク使用量は戻ってきます。MongoDBはいくつかの実用的なオプションを提供します。

単純なタイムスタンプで期限切れになるデータの場合、TTLインデックスが最もクリーンな答えであることがよくあります。

db.sessions.createIndex(
  { expiresAt: 1 },
  { expireAfterSeconds: 0 }
)

このインデックスは、expiresAtに保存された日付以降にドキュメントを削除します。セッション、一時トークン、短期間のインポートジョブ、またはキャッシュされたAPIレスポンスに役立ちます。これはビジネス保持ルールの代わりにはなりません。TTLモニターはバックグラウンドで実行されるため、秒単位の削除を期待せず、削除前に承認ワークフローが必要なデータにTTLを使用しないでください。

ビジネスレコードの場合は、盲目的に削除するのではなくアーカイブします。一般的なパターンは次のとおりです。

  1. 保持期間より古いドキュメントを、より安価なストレージまたはアーカイブデータベースにコピーします。
  2. カウントと重要なフィールドのサンプルを確認します。
  3. プライマリコレクションから小さなバッチで削除します。
  4. ジョブの実行中にレプリケーションラグとディスクメトリクスを監視します。

小さなバッチが重要です。単一の巨大な削除は、レプリケーションのプレッシャーを生み出し、ログを埋め、フィルターが間違っていたことに誰かが気付いた場合にロールバックを困難にする可能性があります。より安全なバッチジョブは、一度に数千のドキュメントを削除し、少しスリープし、_idまたはタイムスタンプで進捗を記録する場合があります。

while (true) {
  const result = db.events.deleteMany({
    createdAt: { $lt: ISODate("2025-01-01T00:00:00Z") },
    archived: true
  });

  print(`deleted ${result.deletedCount}`);
  if (result.deletedCount === 0) break;
  sleep(500);
}

実際の本番スクリプトでは、範囲全体に対してdeleteManyを使用する代わりに制限パターンを追加し、各バッチをログに記録し、レプリケーションラグまたはディスクI/Oがしきい値を超えた場合に自動的に停止します。

単純すぎるように聞こえるインデックスアドバイスに注意する

未使用のインデックスを削除することは、MongoDBのディスク容量を削減する最良の方法の1つですが、「未使用」にはコンテキストが必要です。インデックスは静かな週には未使用に見えても、月末のレポート、バックグラウンドの調整、またはまれなカスタマーサポートワークフローには依然として重要である可能性があります。

$indexStatsを使用してアクセスパターンを確認します。

db.orders.aggregate([{ $indexStats: {} }])

次に、結果をアプリケーションコード、スケジュールされたジョブ、ダッシュボード、サポートクエリと比較します。インデックスが最後の再起動以降使用されていない場合、それはシグナルであり、断定ではありません。削除する前に、サーバーが最近再起動されたかどうか、およびワークロードサンプルに関係するジョブが含まれているかどうかを確認してください。

また、重複する複合インデックスにも注意してください。次のようなインデックスがある場合。

{ customerId: 1 }
{ customerId: 1, createdAt: -1 }
{ customerId: 1, createdAt: -1, status: 1 }

ソート順、クエリフィルター、および短いインデックスが異なるアクセスパターンをサポートしているかどうかを確認した後にのみ、1つを削除できる場合があります。MongoDBは複合インデックスの左プレフィックスを使用できますが、それは最大のインデックスが常に無料の代替品であることを意味するわけではありません。より大きなインデックスはより多くのメモリと書き込みI/Oを消費するため、最も完全に見えるものではなく、ワークロードに適合するものを保持してください。

レプリカセットでの大規模な縮小操作には再同期を優先する

大規模なレプリカセットの場合、オペレーティングシステムのディスク容量を再利用する最もクリーンな方法は、多くの場合、セカンダリを一度に1つずつ再構築することです。基本的な考え方は次のとおりです。

  1. 健全なレプリケーションと最新のバックアップがあることを確認します。
  2. セカンダリを削除または停止します。
  3. そのローカルデータディレクトリをワイプします。
  4. プライマリまたは別の健全なメンバーから再同期させます。
  5. 次のセカンダリに対して繰り返します。
  6. メンテナンスウィンドウ中にプライマリを降格させ、最後に古いプライマリを再構築します。

このアプローチはコマンドを実行するよりも遅いですが、再構築された各メンバーが現在のデータに基づいて新しいストレージファイルを書き込むため、推論が容易です。また、本番トラフィック下ですべてのコレクションを圧縮しようとすることを回避します。無料ではありません。初期同期はネットワークとディスクに負荷がかかる可能性があり、1つのメンバーが再構築されている間、レプリカセットを安全に保つために十分な残りのメンバーが必要です。

スタンドアロンのMongoDBサーバーの場合、そのような余裕はありません。その場合は、メンテナンスウィンドウを計画し、テスト済みのバックアップを作成し、mongodump/mongorestoreまたは新しいボリュームへのファイルシステムレベルの移行を検討してください。より小さなデータディレクトリを望むという理由だけでmongod --repairを選択しないでください。修復は回復ツールとして扱い、日常的なハウスキーピングとしては扱わないでください。

Oplog、ログ、バックアップにも注意する

すべてのMongoDBのディスクプレッシャーがコレクションから発生するわけではありません。レプリカセットでは、oplogはcappedコレクションであるため、永久に成長し続けることはありませんが、設定されたサイズは依然として重要です。小さすぎると、メンテナンス中にセカンダリが脱落する可能性があります。小さなディスクで必要以上に大きい場合、容量を無駄にしている可能性があります。意図的に確認してください。

db.getSiblingDB("local").oplog.rs.stats()

MongoDBログも、スロークエリログ、デバッグの詳細度、またはアプリケーションエラーループがノイズを発生させると、ディスクを満たす可能性があります。ログローテーションを使用し、可能な限りデータベースログをデータを保存する同じ小さなボリュームから遠ざけてください。

バックアップももう1つの一般的な驚きです。チームは便利だからという理由で同じホストにmongodumpを実行し、バックアップウィンドウ中にディスクアラートが発生する理由を疑問に思うことがあります。同じファイルシステムに保存されたバックアップは、バックアップとしてあまり機能せず、すでにリスクの高い操作中にMongoDBをさらに悪い障害に追いやる可能性があります。バックアップをオブジェクトストレージ、バックアップサーバー、または別のマウントされたボリュームにストリーミングしてください。

MongoDBディスクがいっぱいになった場合の実践的なランブック

ディスクがすでに90%を超えている場合は、速度を落として次の順序で作業してください。

  1. MongoDBがまだ書き込みを受け入れているかどうか、およびレプリカセットが健全かどうかを確認します。
  2. プラットフォームが許可する場合は、一時的なディスク容量を追加します。これは多くの場合、緊急削除よりも安全です。
  3. サイズが大きくなりすぎたログとローカルバックアップファイルを移動またはローテーションします。
  4. 大量に書き込んでいる重要でないバッチジョブを停止します。
  5. db.stats()とコレクションstats()を使用して、最大のコレクションとインデックスを特定します。
  6. 明確な保持ルールがあるデータのみをアーカイブまたは削除します。
  7. システムが安定した後、圧縮、再同期、または復元を計画します。

最善の長期的な修正は退屈なものです。保持ルール、インデックスのレビュー、ディスクアラート、テスト済みの再構築手順です。MongoDBは内部の空き領域を再利用することに問題はありませんが、オペレーターはどのデータが高速ストレージに保存される価値があり、どこに移動できるかを決定する必要があります。