磁碟 I/O 效能是影響 MongoDB 資料函式庫整體效率的關鍵因素,尤其在資料量龐大或操作頻繁的環境下。本文旨在提供 MongoDB 磁碟 I/O 調校的最佳實務,涵蓋暫存檔案管理、日誌檔案最佳化、WiredTiger 儲存引擎組態以及資料檔案 I/O 最佳化等導向。此外,文章也將探討如何在複製集和 MongoDB Atlas 環境下進行效能調校,並提供實用的程式碼範例和圖表說明。藉由理解和應用這些技巧,開發者可以有效提升 MongoDB 資料函式庫的效能和穩定性。
磁碟I/O效能調校
在MongoDB的運作中,磁碟I/O是一個至關重要的效能因素。當資料量龐大或操作頻繁時,磁碟I/O可能會成為效能瓶頸,影響整體系統的運作效率。本章節將探討MongoDB中的磁碟I/O相關議題,包括暫存檔案的I/O、Journal機制,以及如何最佳化這些方面的效能。
暫存檔案的I/O
當MongoDB執行某些操作,如大規模的聚合查詢或排序操作時,可能會使用暫存檔案。這些暫存檔案通常儲存在_tmp目錄下。如果這些操作變得極端,可能會干擾到資料檔案和Journal的I/O,從而導致整體效能下降。
最佳化暫存檔案的I/O
增加
internalQueryMaxBlockingSortMemoryUsageBytes引數:透過增加這個引數,可以允許更多的記憶體用於排序操作,從而避免使用暫存檔案。將
_tmp目錄放在高效能的儲存裝置上:考慮將_tmp目錄放在專用的SSD或雲端臨時磁碟上,以提高I/O效能。
Journal機制
MongoDB使用Journal機制來確保資料的一致性和永續性。當資料在WiredTiger快取中被修改時,這些修改首先被寫入Journal檔案。Journal檔案採用預寫日誌(Write-Ahead Log, WAL)的模式,這種模式可以順序寫入資料,從而提高寫入效能。
Journal相關統計資訊
透過db.serverStatus().wiredTiger.log可以取得Journal相關的統計資訊,包括:
log bytes written:寫入Journal的資料量。log sync operations:Journal同步操作的次數。log sync time duration (μsecs):同步操作所花費的時間。
這些統計資訊可以幫助我們瞭解Journal的寫入速率和同步延遲。
計算平均Journal同步時間
var journalStats = db.serverStatus().wiredTiger.log;
var avgSyncTimeMs = journalStats['log sync time duration (usecs)'] / 1000 / journalStats['log sync operations'];
print('Journal avg sync time (ms)', avgSyncTimeMs);
平均Journal同步時間是衡量Journal磁碟爭用的重要指標。一般來說,這個時間越短越好,但具體的最佳值取決於工作負載的特性。
將Journal放在專用裝置上
由於Journal的I/O特性與其他資料檔案不同,且資料函式庫修改通常需要等待Journal寫入完成,因此在某些情況下,將Journal放在專用的高速裝置上可以提高效能。
如何移動Journal到專用裝置
- 掛載新的外部磁碟裝置。
- 將Journal檔案移動到新的裝置上。
這樣可以提高Journal的寫入效能,從而改善整體的資料函式庫效能。
MongoDB 磁碟 I/O 最佳化
日誌檔案最佳化
將日誌檔案(journal)移到獨立的裝置上,可以顯著提高寫入效能。以下是一個範例,展示如何將日誌檔案移到 /dev/sde 裝置:
$ cd /var/lib/mongodb
$ service mongod stop
$ mv journal OldJournal
$ mkdir journal
$ mount /dev/sde journal
$ cp -p OldJournal/* journal
$ chown -R mongod:mongod journal
$ chcon -R -u system_u -t mongod_var_lib_t journal
$ service mongod start
內容解密:
- 停止 MongoDB 服務:
service mongod stop用於停止 MongoDB 服務,以確保在移動日誌檔案時不會有任何寫入操作。 - 掛載新裝置:將
/dev/sde裝置掛載到journal目錄,並將原有的日誌檔案複製到新裝置。 - 設定許可權:使用
chown和chcon設定journal目錄的許可權和 SELinux 上下文,以確保 MongoDB 可以正確存取日誌檔案。 - 啟動 MongoDB 服務:
service mongod start用於重新啟動 MongoDB 服務。
將日誌檔案移到獨立的裝置上後,需要確保該裝置在系統重新啟動後仍能正確掛載,方法是將相關設定新增到 /etc/fstab 檔案中。
資料檔案 I/O
對於大多數資料函式庫而言,讀取操作遠多於寫入操作。即使在更新密集的系統中,資料也需要在寫入前被讀取。只有在工作負載幾乎完全由批次插入組成時,寫入效能才會成為主導因素。
快取與磁碟讀取
WiredTiger 快取在避免磁碟讀取方面發揮著重要作用。如果檔案可以在快取中找到,則不需要從磁碟讀取。對於典型的負載,超過 90% 的檔案讀取可以從快取中滿足。
相關統計資料:
application threads page read from disk to cache count:記錄從磁碟讀取到快取的頁面數量。application threads page read from disk to cache time (usecs):記錄從磁碟讀取到快取所花費的時間(微秒)。
var cache = db.serverStatus().wiredTiger.cache;
var reads = cache['application threads page read from disk to cache count'];
var time = cache['application threads page read from disk to cache time (usecs)'];
print('avg disk read time (ms):', time / 1000 / reads);
內容解密:
- 計算平均磁碟讀取時間:透過
db.serverStatus()取得相關統計資料,計算平均磁碟讀取時間。 - 評估 I/O 子系統健康狀況:平均讀取時間是評估 I/O 子系統健康狀況的重要指標。一般而言,平均讀取時間應小於 10ms,即使用磁碟;如果使用 SSD,則應低於 1ms。
資料檔案寫入
WiredTiger 非同步寫入資料檔案,大多數情況下,應用程式不需要等待這些寫入完成。然而,當寫入 I/O 成為瓶頸時,驅逐(eviction)過程將阻塞操作,直到快取中的髒資料(dirty data)被足夠清除。
相關統計資料:
application threads page write from cache to disk count:記錄從快取寫入到磁碟的頁面數量。application threads page write from cache to disk time (usecs):記錄從快取寫入到磁碟所花費的時間(微秒)。
跨多個裝置分割資料檔案
通常的做法是將所有資料檔案放在單一檔案系統上,由組態為 RAID 10 的磁碟陣列支援。然而,在某些情況下,將特定的資料元素對映到獨立裝置上可能是值得的。
例如,可以將「冷」歸檔資料存放在廉價的磁碟上,而將「熱」資料存放在高效能的 SSD 上。
組態範例:
storage:
dbPath: /mnt/mongodb/mongoData/rs1
directoryPerDB: true
journal:
enabled: true
內容解密:
directoryPerDB: true:將每個資料函式庫的資料檔案存放在獨立的目錄中。directoryForIndexes:雖然未在此範例中展示,但可以設定為true,以將索引和集合檔案存放在獨立的子目錄中。
這種組態使得跨多個裝置分割資料檔案變得更容易,特別是在初始建立資料函式庫時進行規劃。
MongoDB 磁碟 IO 最佳化
WiredTiger 儲存引擎組態
MongoDB 使用 WiredTiger 儲存引擎,其組態對於磁碟 IO 效能有直接影響。以下是一個範例組態:
wiredTiger:
engineConfig:
cacheSizeGB: 16
directoryForIndexes: true
內容解密:
cacheSizeGB: 設定 WiredTiger 快取大小為 16GB。適當的快取大小可以減少磁碟 IO 需求。directoryForIndexes: 將索引檔案存放在獨立的目錄中,有助於 IO 分散和最佳化。
資料儲存結構
在 dbPath 目錄下,MongoDB 的資料儲存結構如下所示:
├── _tmp
├── admin
│ ├── collection
│ │ ├── 13--419801202851022452.wt
│ │ ├── 21--419801202851022452.wt
│ │ └── 23--419801202851022452.wt
│ └── index
│ ├── 14--419801202851022452.wt
│ ├── 22--419801202851022452.wt
│ ├── 24--419801202851022452.wt
│ └── 25--419801202851022452.wt
├── config
│ ├── collection
│ │ ├── 17--419801202851022452.wt
│ │ ├── 19--419801202851022452.wt
│ │ └── 34--419801202851022452.wt
│ └── index
│ ├── 18--419801202851022452.wt
│ ├── 20--419801202851022452.wt
│ ├── 35--419801202851022452.wt
│ └── 36--419801202851022452.wt
├── diagnostic.data
│ └── metrics.2020-10-04T07-12-03Z-00000
├── journal
│ ├── WiredTigerLog.0000000014
│ ├── WiredTigerPreplog.0000000014
│ └── WiredTigerPreplog.0000000015
├── sizeStorer.wt
└── storage.bson
內容解密:
- 資料函式庫目錄結構:每個資料函式庫都有獨立的目錄,包含 collection 和 index 子目錄。
collection和index目錄:分別存放資料檔案和索引檔案,採用 WiredTiger 檔案格式(.wt)。journal目錄:存放日誌檔案,用於保證資料的一致性和永續性。
IO 瓶頸檢測
要檢測 IO 是否過載,可以觀察以下指標:
- 平均讀取時間:從磁碟讀取資料到快取的平均時間不應超過 1-2ms(針對 SSD)。
- 作業系統統計資訊:使用
iostat(Linux)或Get-Counter(Windows)等工具檢查磁碟佇列長度和等待時間。
Linux 下使用 iostat
iostat -xm -o JSON sdc 5 2 | jq
輸出範例:
{
"avg-cpu": {
"user": 45.97,
"nice": 0,
"system": 3.63,
"iowait": 1.81,
"steal": 0,
"idle": 48.59
},
"disk": [
{
"disk_device": "sdc",
"r/s": 0.4,
"w/s": 49.2,
"rkB/s": 15.2,
"wkB/s": 2972,
"rrqm/s": 0,
"wrqm/s": 0.4,
"rrqm": 0,
"wrqm": 0.81,
"r_await": 15.5,
"w_await": 42.55,
"aqu-sz": 2.08,
"rareq-sz": 38,
"wareq-sz": 60.41,
"svctm": 0.87,
"util": 4.32
}
]
}
內容解密:
aqu-sz(佇列大小):較高的值表示磁碟佇列較長,可能意味著磁碟過載。r_await(平均讀取等待時間):超過 10ms 可能表示磁碟效能不足或組態不當。
處理 IO 瓶頸
面對 IO 瓶頸,有兩種主要解決方案:
- 降低 IO 子系統的需求:最佳化查詢、索引和組態 WiredTiger 快取。
- 提升 IO 子系統頻寬:根據硬體平台,採用更快速的磁碟或增加磁碟。
增加 IO 子系統頻寬的方法
- 更換為更快速的磁碟:例如,使用 SLC SSD 取代 MLC SSD 或傳統硬碟。
- 使用 NVMe/PCIe SSD:相較於 SATA/SAS SSD,具有更低的延遲和更高的效能。
- 增加磁碟並分散資料:在有多餘插槽的伺服器上,可以新增磁碟並將資料分散儲存。
提升 MongoDB 效能:複製集與 Atlas 的最佳實踐
在前面的章節中,我們探討了單一 MongoDB 伺服器的效能調校。然而,大多數生產環境中的 MongoDB 例項都是以複製集(Replica Sets)的形式組態,以提供現代「永遠線上」應用程式所需的高用性保證。
複製集基礎
複製集遵循最佳實踐,由一個主要節點(Primary Node)和兩個或多個次要節點(Secondary Nodes)組成。建議使用三個或更多節點,且總節點數為奇數。主要節點接受所有寫入請求,並將其同步或非同步傳播到次要節點。當主要節點故障時,會發生選舉,選出一個次要節點作為新的主要節點,以繼續資料函式庫操作。
複製集的效能影響
在預設組態下,複製集的效能影響最小。所有讀寫操作都將導向主要節點,而主要節點在傳輸資料到次要節點時會產生一些額外負擔,但這種開銷很少是關鍵性的。
儲存陣列最佳化
如果您的 IO 服務由儲存陣列提供,並且遇到 IO 瓶頸,那麼您應該檢查以下幾點:
- 陣列內的裝置型別:一些儲存陣列混合使用磁碟和 SSD 以降低儲存成本。然而,這種混合陣列可能提供不可預測的效能,尤其是對於資料函式庫工作負載。如果可能,您的儲存陣列應該只包含高速 SSD。
- 陣列中的裝置數量:陣列的最大 IO 頻寬取決於陣列中的裝置數量。大多數陣列允許在不停機的情況下新增裝置,這可能是增加陣列 IO 容量的最簡單方法。
- 陣列中使用的 RAID 等級:對於資料函式庫工作負載,RAID 10(「Stripe And Mirror Everything」)幾乎總是正確的 RAID 等級,而 RAID 5 或 6 幾乎總是錯誤的選擇。如果供應商試圖告訴您,他們的 RAID 5 具有某種神奇的技術,可以避免 RAID 5 寫入懲罰,那麼對於資料函式庫工作負載來說,RAID 5 幾乎總是壞訊息。
雲端儲存最佳化
如果您的伺服器執行在 AWS、Azure 或 GCP 等雲端環境中,那麼增加 IO 頻寬的常見方法是重新組態虛擬磁碟。您可以輕鬆更改附加磁碟的型別和組態的 IOPS。在某些情況下,需要重新啟動虛擬機器才能實施更改。
MongoDB Atlas 最佳化
對於根據 Atlas 的伺服器,更改 IO 等級更加簡單。Atlas 控制檯允許您選擇所需的 IOPS 等級。無需重新啟動伺服器,但會發生一系列主要節點降級,以將更改遷移到複製集。
重點提示
- 為了讓依賴儲存陣列 IO 的資料函式庫伺服器獲得最佳效能,請確保使用的裝置是高速 SSD,裝置數量足以滿足 IO 需求,並且 RAID 組態是 RAID 10,而不是 RAID 5 或 RAID 6。
- 在 AWS、Azure、GCP 或 Atlas 上更改雲端 MongoDB 伺服器的 IO 等級,可以透過幾次點選完成,有時無需停機。
圖表說明
圖 12-10:更改 AWS 磁碟區的 IOPS
此圖示展示瞭如何在 AWS 中調整 EBS 磁碟區的最大 IOPS。
圖 12-11:調整 Atlas 伺服器的 IO
此圖示展示瞭如何在 MongoDB Atlas 中調整 IO 等級。
程式碼說明
// 調整 MongoDB Atlas 中的 IO 等級範例
const adjustIO = async () => {
// 連線到 MongoDB Atlas
const client = new MongoClient(process.env.ATLAS_URI);
try {
await client.connect();
// 調整 IO 等級
const adminDb = client.db('admin');
const result = await adminDb.command({
setClusterParameter: {
diskSizeGB: 100,
// 其他引數...
}
});
console.log(result);
} catch (err) {
console.error(err);
} finally {
await client.close();
}
};
adjustIO();
程式碼解密:
- 連線到 MongoDB Atlas:使用
MongoClient連線到 MongoDB Atlas。 - 調整 IO 等級:透過執行
setClusterParameter命令來調整 IO 等級,例如更改diskSizeGB。 - 處理結果:輸出命令執行結果或錯誤訊息。
- 關閉客戶端:完成操作後關閉
MongoClient連線。
Replica Sets 與 Atlas 的效能挑戰與機會
- 額外效能挑戰:在 Replica Sets 中,主要節點和次要節點之間的資料同步可能會帶來額外的效能挑戰。
- 效能機會:合理組態 Replica Sets 和使用 MongoDB Atlas 可以提供額外的效能機會,例如提高用性和可擴充套件性。