TTL (Time-To-Live) インデックスは、MongoDB がドキュメントの期限切れ後に自動的に削除するために使用する特殊な単一フィールドインデックスです。 このトピックでは、TTL インデックスの仕組み、作成方法と管理方法、制限、一般的な問題と解決策、およびApsaraDB for MongoDB で TTL インデックスを使用するためのベストプラクティスについて説明します。
概要
TTL (Time-To-Live) インデックスは、MongoDB がドキュメントを指定された有効期限後に自動的に削除するために使用する、特別な単一フィールドインデックスです。TTL インデックスは、ログレコード、セッション情報、一時キャッシュ、認証コードなど、ライフサイクルが明確に定義されたデータの管理に適しています。TTL インデックスを適切に利用することで、コレクションのデータ量を効果的に制御し、ストレージの無制限な増加を防止できます。
ただし、本番環境で TTL インデックスを不適切に使用すると、周期的な CPU スパイク、期限切れデータの削除の遅延、ディスク容量の継続的な増加など、さまざまな問題が発生する可能性があります。このトピックでは、ApsaraDB for MongoDB で TTL インデックスを正しく使用できるように、TTL インデックスの仕組み、使用方法、一般的な問題、およびベストプラクティスについて説明します。
仕組み
基本メカニズム
mongod プロセスが起動すると、TTLMonitor という名前のバックグラウンドスレッドが作成されます。デフォルトでは、このスレッドは 60 秒ごとに TTL クリーンアップを 1 回実行します。各ラウンドでは、次の操作が実行されます:
現在のデータベース内にあるすべての TTL インデックスを収集します。
各 TTL インデックスに対して順に実行計画を生成し、データのクリーンアップを実行します。
インデックス付きフィールドの値に
expireAfterSecondsを加算した値が、現在時刻より前であるドキュメントを削除します。
有効期限のしきい値は、インデックス付きフィールドの日付値に expireAfterSeconds で指定された秒数を加算して計算されます。その結果が現在時刻より前の場合、ドキュメントは有効期限が切れたと見なされます。
レプリカセットでの動作
レプリカセットでは、TTL のバックグラウンドスレッドはプライマリーノードでのみ削除操作を実行します。セカンダリーノード上の TTL スレッドはアイドル状態のままで、プライマリーノードから oplog をレプリケーションすることで削除操作を同期します。つまり、TTL 削除は追加の oplog エントリを生成し、レプリカセットのレプリケーションラグに影響する可能性があります。
バージョン差分:バッチ削除の最適化 (MongoDB 7.0 以降)
MongoDB 7.0 (開発版 6.1 で最初に導入された機能) 以降、TTL の削除では「フェア削除」メカニズムが使用され、各 TTL インデックスに削除時間がタイムスライス方式で割り当てられます。これにより、特定のコレクションの期限切れデータがクリーンアップされないまま放置されるのを防ぎます。主な改善点は次のとおりです:
ttlMonitorBatchDeletes:バッチ削除モードを有効にします。デフォルトで有効です。有効な場合、TTL の削除はコレクション間でより公平に分散されます。
ttlIndexDeleteTargetTimeMS:各 TTL インデックスに対する 1 ラウンドあたりの削除時間の上限です。デフォルト値:1000 ms。
ttlIndexDeleteTargetDocs:各 TTL インデックスに対する 1 ラウンドあたりの削除ドキュメント数の上限です。デフォルト値:50,000。
ttlMonitorSubPassTargetSecs:TTL インデックスを反復処理するサブパスの時間上限です。デフォルト値:60 秒。
これらのパラメーターは、実行計画の BATCHED_DELETE ステージとして表示され、explain コマンドを使用して表示できます。
TTL インデックスの作成と管理
TTL インデックスの作成
createIndex メソッドを使用して、Date 型のフィールドに TTL インデックスを作成します。
例 1: lastModifiedDate フィールドの値から 3600 秒後にドキュメントが期限切れになるように TTL インデックスを作成します。
db.eventlog.createIndex(
{ "lastModifiedDate": 1 },
{ expireAfterSeconds: 3600 }
)
例 2: 特定の時点で期限切れにする。 expireAfterSeconds を 0 に設定して、フィールド値のみが有効期限を決定するようにします。
db.sessions.createIndex(
{ "expireAt": 1 },
{ expireAfterSeconds: 0 }
)
TTL インデックスの有効期限の変更
collMod コマンドを使用すると、インデックスを再構築することなく、既存の TTL インデックスの expireAfterSeconds 値を変更できます。
db.runCommand({
"collMod": "log_events",
"index": {
"keyPattern": { "createdAt": 1 },
"expireAfterSeconds": 600
}
})
通常インデックスから TTL インデックスへの変換 (MongoDB 6.0 以降)
MongoDB 6.0 以降では、collMod コマンドを使用して、既存の通常の単一フィールドインデックスを最初に削除して再構築することなく、TTL インデックスに変換できます。
db.runCommand({
"collMod": "tickets",
"index": {
"keyPattern": { "lastModifiedDate": 1 },
"expireAfterSeconds": 100
}
})
TTL ステータスの監視
次のコマンドを使用して TTL の操作を監視できます。
// TTL によって削除されたドキュメントの総数を表示
db.serverStatus().metrics.ttl.deletedDocuments
// TTL スレッドのパス回数を表示
db.serverStatus().metrics.ttl.passes
// TTL のスキャン間隔を表示 (デフォルト:60 秒)
db.runCommand({ getParameter: 1, ttlMonitorSleepSecs: 1 })
// 現在実行中の TTL 削除操作を表示
db.currentOp()
制限事項
TTL インデックスを使用する前に、次の重要な制限事項を把握する必要があります:
TTL インデックスがサポートするのは、単一フィールドインデックスのみです。複合インデックスでは TTL 機能を使用できません。たとえ
expireAfterSecondsを設定しても、無視されます。_id フィールドは TTL インデックスをサポートしません。
インデックス対象フィールドは BSON Date 型である必要があります。 フィールド値が Date 型でない場合 (例:UNIX タイムスタンプの整数や文字列)、ドキュメントは自動的に削除されません。
インデックス対象フィールドを含まないドキュメントは期限切れになりません。
フィールドが配列の場合、配列内で最も早い日付値が有効期限の計算に使用されます。
既存の TTL インデックスは、
createIndexを使用して変更することはできません。collModコマンドを使用する必要があります。expireAfterSecondsは 0~2,147,483,647 の範囲内である必要があり、NaNに設定することはできません。そうでない場合、予期しない動作やデータ損失が発生する可能性があります。
一般的な問題と解決策
問題1:TTL 削除が原因の定期的な CPU スパイク
症状
インスタンスの CPU 使用率には、一定の間隔で明らかな周期的なピークが見られます。db.currentOp() を実行すると、TTL スレッドが大量の削除操作を実行していることを確認できます。
原因
アプリケーションが同一のタイミングで大量のデータを挿入し、TTL フィールドの値が同一または近い場合、それらのドキュメントは同じ時間帯に期限切れになります。TTL スレッドは 1 回のスキャンで大量のドキュメントを削除する必要があるため、CPU と I/O に大きな負荷がかかります。
解決策
有効期限を分散させる:データ挿入時に TTL フィールドにランダムなオフセットを追加し、有効期限を一定の時間幅に分散させて一括削除を回避します。たとえば、24 時間で期限切れになるデータの場合、有効期限に数分のランダムなオフセットを加算または減算します。
MongoDB 7.0 以降にアップグレードする:バッチ削除とフェア削除メカニズムを使用して、削除負荷を平準化します。
適切なインスタンス仕様を選択する:TTL 削除による追加負荷を処理できるよう、十分な CPU とメモリを備えたインスタンスを選択してください。
例:挿入データにランダムな有効期限オフセットを追加します。
// 24 時間に 0~600 秒のランダムオフセットを追加
var randomOffset = Math.floor(Math.random() * 600);
var expireTime = new Date(Date.now() + (86400 + randomOffset) * 1000);
db.logs.insertOne({
data: "log content",
createdAt: expireTime
})
問題2:有効期限切れ後にデータが削除されない
症状
ドキュメントが想定した有効期限を過ぎても、コレクション内に残っています。
トラブルシューティング手順
-
フィールド型を確認する:TTL インデックス対象フィールドの値が BSON Date 型かどうかを確認します。よくある誤りとして、Date オブジェクトではなく UNIX タイムスタンプの整数を使用するケースがあります。
// フィールド型を確認 db.collection.findOne({}, { createdAt: 1 }) // 正:ISODate("2024-01-01T00:00:00Z") // 誤:1704067200 (整数のタイムスタンプ) フィールドの存在を確認する:ドキュメントに TTL インデックス対象フィールドが実際に含まれているかどうかを確認します。フィールドを含まないドキュメントは自動的に削除されません。
-
インデックス定義の確認: インデックスに
expireAfterSecondsプロパティが含まれていることを確認します。db.collection.getIndexes() インデックスの方向の確認:TTL インデックスが昇順 (値は
1) であることを確認してください。 降順インデックスの場合、TTL が誤動作する可能性があります。 インデックスの方向が正しくない場合は、インデックスを削除して再作成してください。-
TTL モニターが有効になっているか確認します:
ttlMonitorEnabledパラメーターがtrueに設定されていることを確認します。db.adminCommand({ getParameter: 1, ttlMonitorEnabled: 1 })
問題3:TTL 削除がデータ挿入レートに追いつかない
症状
TTL インデックスを設定しているにもかかわらず、コレクション内のデータ量が増え続け、ディスク使用量も増加し続けます。
原因
TTL スレッドはシングルスレッドで、60 秒ごとに実行されます。データ挿入レートが TTL のクリーンアップレートを大きく上回ると、期限切れデータが蓄積します。さらに、インスタンスに複数の TTL インデックスが存在する場合は直列に処理されるため、クリーンアップ効率がさらに低下します。
解決策
-
スキャン頻度の調整:
ttlMonitorSleepSecsパラメーターを設定することで、スキャン間隔を短縮できます。なお、頻度を高くするとシステム負荷が増加します。// スキャン間隔を 10 秒に設定 db.adminCommand({ setParameter: 1, ttlMonitorSleepSecs: 10 }) アプリケーション側でクリーンアップする:書き込み量が特に多いシナリオでは、TTL インデックスに全面的に依存するのではなく、アプリケーションで定期的なバッチ削除を実装してください。
時間でパーティション分割されたコレクションを検討する:非常に高い書き込みシナリオでは、時間単位でデータをコレクションに分割し、期限切れコレクションを丸ごと削除します。TTL インデックスでドキュメントを 1 件ずつ削除するよりもはるかに効率的です。
問題4:削除後にディスク領域が解放されない
症状
TTL によって多数のドキュメントが削除されたにもかかわらず、ディスク使用量が大きく減少しません。
解決策
MongoDB は WiredTiger ストレージエンジンを使用します。ドキュメントが削除されると、その領域は再利用可能としてマークされますが、すぐにオペレーティングシステムに返却されるわけではありません。ディスク領域をすぐに解放するには、compact コマンドを実行するか、初期同期によってデータファイルを再構築します。オフピーク時間帯に compact を実行することをお勧めします。
ベストプラクティス
1. 正しいデータ型の確保
TTL インデックスは BSON Date 型のフィールドでのみ機能します。アプリケーション側で TTL フィールドの正しい日付型を強制するか、MongoDB のスキーマ検証機能を使用することを推奨します。
db.createCollection("sessions", {
validator: {
$jsonSchema: {
properties: {
expireAt: { bsonType: "date", description: "TTL フィールドは Date 型である必要があります" }
},
required: ["expireAt"]
}
}
})
2. 有効期限を分散して一括削除を回避
これは、TTL 削除による CPU スパイクを防止する最も効果的な方法です。データ挿入時に有効期限にランダムなオフセットを追加し、まとまったデータが一定の時間範囲に分散して期限切れになるようにします。これにより、TTL スレッドはスキャンごとに少数のドキュメントのみを削除するため、システムパフォーマンスへの影響を大幅に低減できます。
保持期間が長いデータ (24 時間以上など) については、有効期限に 0~10 分のランダムなオフセットを追加することを推奨します。
3. expireAfterSeconds の慎重な調整
既存の TTL インデックスの expireAfterSeconds の値を小さくすると、多数の既存ドキュメントがすぐに期限切れになり、大規模な削除操作がトリガーされ、インスタンスのパフォーマンスに深刻な影響を与える可能性があります。以下を推奨します。
調整後に即時期限切れとなるドキュメント数を見積もります。
変更はオフピーク時間に行ってください。
期限切れのドキュメントの数が多い場合は、まずスクリプトを使用してデータの一部をバッチで削除し、その後で
expireAfterSecondsを変更します。
4. 新規 TTL インデックス作成時の注意点
すでに有効期限条件を満たすドキュメントが大量に存在するコレクションに TTL インデックスを作成すると、インデックス構築直後に大規模な削除が発生する可能性があります。次の対応を推奨します:
オフピーク時間に TTL インデックスを作成します。
先に過去の期限切れデータをクリーンアップし、その後 TTL インデックスを作成します。
通常の業務に影響が及ばないように、インデックスはバックグラウンドで構築します。
5. TTLMonitor を長期間無効化しない
ttlMonitorEnabled パラメーターを設定して TTL 機能を一時的に無効にすることはできますが、本番環境で長期間無効にしたままにすることはお勧めしません。無効期間中に蓄積された大量の期限切れドキュメントは、機能が再度有効になったときに一括で削除され、深刻なパフォーマンスの問題を引き起こす可能性があります。
TTLMonitor を一時的に無効化した場合は、期限切れデータの蓄積を防ぐため、速やかに再有効化してください。
6. TTL が oplog に与える影響の評価
TTL によって削除された各ドキュメントは oplog エントリを生成します。削除量が多い場合、oplog の書き込み量が大幅に増加し、レプリカセットのレプリケーションラグに影響する可能性があります。次の方法で影響を評価することを推奨します:
metrics.ttl.deletedDocumentsを監視して TTL 削除の量を追跡します。レプリケーションラグを監視し、セカンダリーノードが TTL によって生成される追加の oplog を処理できることを確認します。
必要に応じて oplog サイズを増やし、TTL 削除による oplog のオーバーフローを防止します。
7. 高書き込みシナリオにおける代替案
TTL インデックスは、書き込み量が中程度のシナリオに適しています。書き込み量が極めて多いシナリオでは、次の代替案を検討してください:
時間でパーティション分割されたコレクション:時間軸 (日単位、週単位など) でコレクションを分割し、期限切れコレクション全体を削除します。これはデータを期限切れにする最も効率的な方法です。
時系列コレクション:MongoDB 5.0 以降では時系列コレクションをサポートします。時系列コレクションにはデータの有効期限機能が組み込まれており、バケット単位で一括で削除されます。通常のコレクションでドキュメントを 1 件ずつ削除するよりもはるかに効率的です。
アプリケーション側の定期クリーンアップ:オフピーク時間にバッチ削除を実行するスケジュールタスクを使用します。これにより、削除レートとタイミングをより正確に制御できます。
バージョン別の TTL 機能
バージョン |
機能 |
6.0 以降 |
|
7.0 以降 |
削除効率を向上させるバッチ削除メカニズム (BATCHED_DELETE) を導入し、特定のコレクションが「飢餓状態」になるのを防ぐ公平な削除を実装し、時系列コレクションの部分的な TTL インデックスと、より柔軟な |
TTL パラメーターリファレンス
パラメーター |
説明 |
デフォルト値 |
|
TTL スレッドのスキャン間隔です。 |
60 秒 |
|
TTL 機能のスイッチです。 |
true |
|
バッチ削除モード (7.0 以降) です。 |
true |
|
1 ラウンドあたりの削除時間の上限 (7.0 以降) です。 |
1000 ms |
|
1 ラウンドあたりの削除ドキュメント数の上限 (7.0 以降) です。 |
50,000 |
|
サブパスの時間上限 (7.0 以降) です。 |
60 秒 |
まとめ
TTL インデックスは、ライフサイクルが定義されたデータを効果的に管理できる、MongoDB における強力で実用的な自動データ期限切れメカニズムです。本番環境では、パフォーマンス問題を回避するために、ビジネスシナリオに基づいて設定してください。重要なポイントは次のとおりです:
TTL フィールドが BSON Date 型であることを確認してください。これは TTL インデックスが正しく機能するための前提条件です。
有効期限の分散は、CPU スパイクを回避する最も効果的な方法です。
TTL のステータス (削除量、スキャンパス、レプリカセットのラグ) を継続的に監視してください。
高書き込みシナリオでは、時間でパーティション分割されたコレクションや時系列コレクションなどの代替案を検討してください。
新しいバージョン (7.0 以降など) へアップグレードすると、TTL 削除のパフォーマンスと公平性が向上します。