TTL(Time-To-Live)索引是 MongoDB 提供的一種特殊單欄位索引,可在文檔到期後自動刪除。本文介紹 TTL 索引的工作原理、建立與管理方法、使用限制、常見問題與解決方案,以及在ApsaraDB for MongoDB上的最佳實務建議。
概述
TTL(Time-To-Live)索引是 MongoDB 提供的一種特殊單欄位索引,能夠在文檔達到指定到期時間後自動將其刪除。它適合管理具有明確生命週期的資料,例如日誌記錄、會話資訊、臨時緩衝、驗證碼等情境。通過合理使用 TTL 索引,可以有效控制集合的資料量,避免儲存空間無限增長。
在生產環境中,TTL 索引的不當使用可能引發多種問題,包括 CPU 週期性飆高、到期資料未被及時刪除、磁碟空間持續增長等。本文將從 TTL 索引的工作原理、使用方法、常見問題及最佳實務等方面進行全面介紹,協助您在ApsaraDB for MongoDB上正確使用 TTL 索引。
TTL 索引的工作原理
基本機制
每個 mongod 進程啟動時,會建立一個名為 TTLMonitor 的後台線程,該線程預設每隔 60 秒發起一輪 TTL 清理操作。每輪操作的流程如下:
搜集當前資料庫中所有的 TTL 索引。
依次對每個 TTL 索引產生執行計畫並執行資料清理。
刪除索引欄位值加上
expireAfterSeconds小於目前時間的文檔。
到期閾值的計算方式為:索引欄位的日期值 + expireAfterSeconds 的秒數。如果該結果小於目前時間,則文檔被認為已到期。
複本集中的行為
在複本集架構中,TTL 後台線程僅在 Primary 節點上執行刪除操作。Secondary 節點的 TTL 線程處於空閑狀態,通過複製 Primary 的 oplog 來同步刪除操作。這意味著 TTL 刪除會產生額外的 oplog,可能影響複本集的同步延遲。
版本差異:大量刪除最佳化(MongoDB 7.0+)
從 MongoDB 7.0 開始(對應開發版本 6.1 引入的特性),TTL 刪除操作引入了"公平刪除"機制,通過類似"時間片"的方式為每個 TTL 索引分配刪除時間,避免某些集合的到期資料長期得不到清理。主要改進包括:
ttlMonitorBatchDeletes:啟用大量刪除模式,預設開啟。開啟後 TTL 刪除會更加公平地分配到各個集合。
ttlIndexDeleteTargetTimeMS:每個 TTL 索引每輪刪除的時間上限,預設 1000 ms。
ttlIndexDeleteTargetDocs:每個 TTL 索引每輪刪除的文檔數上限,預設 50000。
ttlMonitorSubPassTargetSecs:子輪次遍曆 TTL 索引的時間上限,預設 60 秒。
這些參數在執行計畫中體現為 BATCHED_DELETE 階段,可通過 explain 命令查看。
建立和管理 TTL 索引
建立 TTL 索引
使用 createIndex 方法,在日期類型欄位上建立 TTL 索引。
樣本 1:建立 TTL 索引,文檔在 lastModifiedDate 欄位值的 3600 秒後到期。
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 索引僅支援單欄位索引。複合索引無法使用 TTL 功能,即使設定了
expireAfterSeconds也會被忽略。_id 欄位不支援 TTL 索引。
索引欄位必須為 BSON Date 類型。如果欄位值不是 Date 類型(如 UNIX 時間戳記整數、字串等),文檔將不會被自動刪除。
不包含索引欄位的文檔不會到期。
如果欄位是數群組類型,使用數組中最早的日期值來計算到期時間。
不能通過
createIndex修改已有 TTL 索引,必須使用collMod命令。expireAfterSeconds的取值範圍為 0 到 2,147,483,647,且不得設為NaN,否則可能導致不可預期的行為和資料丟失。
常見問題與解決方案
問題一:TTL 刪除導致 CPU 週期性飆高
問題現象
執行個體 CPU 使用率出現明顯的周期性峰值,每隔固定時間間隔出現一次。通過 db.currentOp() 可以觀察到 TTL 線程正在執行大量刪除操作。
原因分析
當業務在同一時刻集中插入大量資料,並且這些資料的 TTL 欄位值相同或接近時,它們會在同一時間視窗內集中到期。TTL 線程在單次掃描中需要刪除大量文檔,會導致顯著的 CPU 和 IO 壓力。
解決方案
打散到期時間:在插入資料時,為 TTL 欄位增加隨機位移量,將到期時間分散到一個時間視窗內,避免集中刪除。例如,對於 24 小時到期的資料,可在到期時間上加減數分鐘的隨機位移。
升級到 MongoDB 7.0 及以上版本:利用大量刪除和公平刪除機制,平滑刪除壓力。
選擇合適的執行個體規格:確保執行個體的 CPU 和記憶體資源足以應對 TTL 刪除帶來的額外負載。
樣本:為插入的資料添加隨機到期位移。
// 在 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
})問題二:資料到期後未被刪除
問題現象
文檔已超過預期的到期時間,但仍然存在於集合中。
排查步驟
確認欄位類型:檢查 TTL 索引欄位的值是否為 BSON Date 類型。一個常見錯誤是使用 UNIX 時間戳記整數而非 Date 對象。
// 檢查欄位類型 db.collection.findOne({}, { createdAt: 1 }) // 正確樣本:ISODate("2024-01-01T00:00:00Z") // 錯誤樣本:1704067200 (整數時間戳記)確認欄位是否存在:查看文檔中是否實際包含了 TTL 索引欄位。不包含該欄位的文檔不會被自動刪除。
檢查索引定義:確認索引確實包含
expireAfterSeconds屬性。db.collection.getIndexes()檢查索引方向:確保 TTL 索引是正序(值為
1)。倒序索引可能導致 TTL 功能異常。如發現索引方向不正確,刪除後重建即可。檢查 TTL Monitor 是否開啟:確認
ttlMonitorEnabled參數為true。db.adminCommand({ getParameter: 1, ttlMonitorEnabled: 1 })
問題三:TTL 刪除速度跟不上資料插入速度
問題現象
集合資料量持續增長,磁碟空間不斷膨脹,即使已配置 TTL 索引。
原因分析
TTL 線程是單線程且每 60 秒執行一次。當資料插入的速率遠超過 TTL 清理速率時,到期資料會不斷累積。此外,如果執行個體上存在多個 TTL 索引,它們之間是串列執行的,進一步降低清理效率。
解決方案
調整掃描頻率:可以通過
ttlMonitorSleepSecs參數縮短掃描間隔,但需注意更高頻率會增加系統負載。// 將掃描間隔調整為 10 秒 db.adminCommand({ setParameter: 1, ttlMonitorSleepSecs: 10 })業務層面主動清理:對於資料寫入量特別大的情境,建議在業務層實現定時大量刪除邏輯,而不是完全依賴 TTL 索引。
考慮使用時間分區集合:對於極高寫入情境,可以按時間分集合儲存,直接刪除整個到期集合,這比 TTL 索引逐條刪除文檔高效得多。
問題四:刪除後磁碟空間未釋放
問題現象
TTL 刪除了大量文檔,但磁碟使用率並未顯著下降。
解決方案
MongoDB 使用 WiredTiger 儲存引擎,刪除文檔後空間會被標記為可重用,但不會立即返還給作業系統。如果需要立即回收磁碟空間,可以執行 compact 命令或通過初始化同步來重新構建資料檔案。建議在業務低峰期進行 compact 操作。
最佳實務建議
一、確保資料類型正確
TTL 索引僅對 BSON Date 類型的欄位生效。建議在應用程式層或通過 MongoDB 的 Schema Validation 功能強制確保 TTL 欄位為正確的日期類型。
db.createCollection("sessions", {
validator: {
$jsonSchema: {
properties: {
expireAt: { bsonType: "date", description: "TTL 欄位必須為 Date 類型" }
},
required: ["expireAt"]
}
}
})二、分散到期時間,避免集中刪除
這是避免 TTL 刪除導致 CPU 飆高的最有效方法。在插入資料時,為到期時間添加隨機位移量,使同一批資料的到期時間分散到一個時間範圍內。這樣 TTL 線程每次掃描時只需要刪除少量文檔,顯著降低對系統效能的影響。
對於保留時間較長的資料(如 24 小時以上),建議在到期時間上添加 0 到 10 分鐘的隨機位移。
三、謹慎調整 expireAfterSeconds
降低已有 TTL 索引的 expireAfterSeconds 值會導致大量已有文檔立即到期,可能觸發大規模刪除操作,嚴重影響執行個體效能。建議的做法是:
先估算調整後會有多少文檔立即到期。
在業務低峰期操作。
如果到期文檔數量巨大,建議先通過業務指令碼分批刪除部分資料,再修改
expireAfterSeconds。
四、建立 TTL 索引時的注意事項
在一個已經包含大量符合到期條件的文檔的集合上建立 TTL 索引,可能導致索引建立完成後立即觸發大規模刪除。建議:
在業務低峰期建立 TTL 索引。
先清理歷史到期資料,再建立 TTL 索引。
使用後台方式建索引,避免影響正常業務。
五、不要長期關閉 TTL Monitor
雖然可以通過 ttlMonitorEnabled 參數臨時關閉 TTL 功能,但不建議在生產環境長期使用。關閉期間累積的大量到期文檔會在重新開啟時集中刪除,可能導致嚴重的效能問題。
臨時關閉 TTL Monitor 後,務必及時重新開啟,避免到期資料大量堆積。
六、合理評估 TTL 對 oplog 的影響
TTL 刪除的每一條文檔都會產生一條 oplog。當刪除量較大時,會顯著增加 oplog 的寫入量,可能影響複本集的同步延遲。建議通過以下方式評估影響:
監控
metrics.ttl.deletedDocuments,瞭解 TTL 刪除量。監控複本集同步延遲,確保 Secondary 能處理 TTL 產生的額外 oplog。
必要時調大 oplog 大小,以避免因 TTL 刪除導致 oplog 溢出。
七、高寫入情境的替代方案
TTL 索引適用於中等寫入量的情境。對於極高寫入量的情境,建議考慮以下替代方案:
時間分區集合:按時間維度拆分集合(如按天、按周),直接 drop 到期集合,這是最高效的資料到期方式。
時間序列集合:MongoDB 5.0+ 支援時間序列集合,內建資料到期功能,按桶大量刪除,效率遠高於普通集合的逐條刪除。
業務層定時清理:通過定時任務在業務低峰期執行大量刪除,可以更精細地控制刪除速率和時機。
各版本 TTL 特性參考
版本 | 特性說明 |
6.0+ | 支援通過 |
7.0+ | 引入大量刪除機制(BATCHED_DELETE),提升刪除效率;實現公平刪除,避免某些集合被"餓死";支援時間序列集合的部分 TTL 索引,支援更靈活的 |
TTL 相關參數速查
參數名 | 說明 | 預設值 |
| TTL 線程掃描間隔。 | 60 秒 |
| TTL 功能開關。 | true |
| 大量刪除模式(7.0+)。 | true |
| 每輪刪除時間上限(7.0+)。 | 1000 ms |
| 每輪刪除文檔數上限(7.0+)。 | 50000 |
| 子輪遍曆時間上限(7.0+)。 | 60 秒 |
總結
TTL 索引是 MongoDB 中一個強大且實用的自動資料到期機制,能夠有效地管理具有明確生命週期的資料。在生產環境中,需要結合業務情境合理配置,以避免效能問題。以下是核心要點:
確保 TTL 欄位類型為 BSON Date,這是 TTL 索引正常工作的前提。
打散到期時間是避免 CPU 毛刺的最有效手段。
持續監控 TTL 的運行狀態(刪除量、掃描輪次、複本集同步延遲)。
對於高寫入情境,考慮使用時間分區集合或時間序列集合等替代方案。
升級到較新版本(如 7.0+)可獲得更好的 TTL 刪除效能和公平性。