返回文章列表

MongoDB WiredTiger 記憶體調校最佳實務

本文探討 MongoDB WiredTiger 儲存引擎的記憶體調校技巧,包含快取大小調整、命中率監控、Eviction 策略最佳化以及檢查點的影響,提供最佳化 MongoDB 效能的實務。

資料函式庫 效能調校

WiredTiger 儲存引擎的記憶體組態對 MongoDB 效能至關重要。理解 WiredTiger 快取機制、調整快取大小、監控命中率及有效管理 Eviction 策略能顯著提升資料函式庫效能。預設的快取大小設定為系統記憶體一半減 1GB 或 256MB,取較大值,但這並非最佳設定。透過 wiredTigerCacheSizeGB 引數或指令動態調整快取大小,並監控命中率,可以找到最佳平衡點。過小的快取會增加磁碟 IO,而過大的快取則可能導致系統交換,反而降低效能。除了快取大小,調整 Eviction 策略和檢查點頻率也能最佳化效能。透過 eviction_dirty_triggereviction_dirty_target 等引數控制髒資料比例,並調整 checkpoint.wait 引數設定檢查點間隔,可以有效降低磁碟 IO 壓力。此外,檔案設計、索引和交易管理也對記憶體使用有顯著影響。合理的 Schema 設計、建立適當的索引和有效管理交易可以減少記憶體消耗,進一步提升資料函式庫效能。

WiredTiger 記憶體調校

絕大多數的 MongoDB 正式環境都採用 WiredTiger 儲存引擎。對於這些環境而言,最大的記憶體區塊將被 WiredTiger 快取佔據。本章節僅討論 WiredTiger 儲存引擎,因為其他儲存引擎的使用率遠低於 WiredTiger。

快取大小對效能的影響

WiredTiger 快取對伺服器效能有著巨大的影響。沒有快取的情況下,每次資料讀取都將變成磁碟讀取。快取通常能減少超過 90% 的磁碟讀取次數,從而使處理量得到數量級的提升。

快取大小的預設值

預設情況下,WiredTiger 快取的大小被設定為總記憶體的一半減去 1GB,或者是 256MB,以較大者為準。例如,在一台 16GB 的伺服器上,預設的快取大小將是 7GB((16/2)- 1)。剩餘的記憶體將用於排序和聚合區域、連線記憶體以及作業系統記憶體。

調整快取大小

WiredTiger 快取大小的預設值是一個有用的起點,但很少是最佳值。如果同一主機上執行著其他工作負載,快取大小可能會太高。相反,在專門用於 MongoDB 的大記憶體系統上,快取大小可能會太低。鑒於 WiredTiger 快取對效能的重要性,您應該準備好調整快取大小以滿足您的需求。

設定快取大小

wiredTigerCacheSizeGB 這個 MongoDB 組態引數控制著快取的最大大小。在 MongoDB 組態檔案中,這對應於 storage/WiredTiger/engineConfig/cacheSizeGB 路徑。例如,要將快取大小設定為 12GB,您需要在 mongod.conf 檔案中指定以下內容:

storage:
  wiredTiger:
    engineConfig:
      cacheSizeGB: 12

您可以在執行中的伺服器上調整 WiredTiger 快取的大小。以下命令將快取大小調整為 8GB:

db.getSiblingDB('admin').runCommand({
  setParameter: 1,
  wiredTigerEngineRuntimeConfig: 'cache_size=8G'
});

如何確定最佳快取大小

太小的快取可能導致 IO 增加,從而降低效能。另一方面,將快取大小增加到超出可用作業系統記憶體會導致交換,甚至更糟糕的效能下降。

越來越多的 MongoDB 被佈署在雲容器中,可以動態調整可用記憶體量。即便如此,在雲環境中,記憶體往往是最昂貴的資源,因此在沒有證據的情況下「增加更多記憶體」是不可取的。

快取命中率

要確定正確的快取記憶體量,沒有一個明確的方法,但我們有一些指標可以指導我們。其中最重要的兩個指標是:

  • 快取「命中」率
  • 驅逐率

資料函式庫快取命中率

資料函式庫快取命中率是一個具有悠久歷史的指標。簡而言之,快取命中率描述了您在記憶體中找到所需資料區塊的頻率:

CacheHitRatio = (Number of IO requests that were satisfied in the cache) / Total IO requests

快取命中率代表了由資料函式庫快取滿足的區塊請求比例,而無需進行磁碟讀取。每一次「命中」(當區塊在記憶體中找到時)都是一件好事,因為它避免了耗時磁碟 IO。因此,直覺上看來,高緩衝區快取命中率也是一件好事。

不幸的是,雖然快取命中率明顯測量了一些東西,但高快取命中率並不總是或甚至通常表明資料函式庫調整良好。特別是,調整不佳的工作負載通常會一遍又一遍地讀取相同的資料區塊;這些區塊幾乎肯定在記憶體中,因此最嚴重低效的操作反而往往會產生非常高的快取命中率。一位 Oracle DBA Connor McDonald 曾經建立了一個可以生成任何所需命中率的指令碼,基本上是透過一遍又一遍地讀取相同的區塊來實作。Connor 的指令碼沒有執行任何有用的工作,但可以實作幾乎完美的命中率。

計算 WiredTiger 快取命中率

對於一個調整良好的工作負載(具有合理的結構設計、適當的索引和最佳化的聚合管道),觀察 WiredTiger 命中率可以讓您瞭解 WiredTiger 快取如何維持您的 MongoDB 工作負載需求。

以下是計算命中率的指令碼:

var cache = db.serverStatus().wiredTiger.cache;
var missRatio = cache['pages read into cache'] * 100 / cache['pages requested from the cache'];
var hitRatio = 100 - missRatio;
print(hitRatio);

這個計算傳回自伺服器啟動以來到目前為止的快取命中率。要計算較短時間內的速率,您可以使用以下命令:

mongoTuning.monitorServerDerived(5000, /cacheHitRate/)

結果顯示,前 5 秒內的快取命中率為 58%。

圖表分析:WiredTiger 快取大小、未命中率和處理量

如果我們的負載運作良好,那麼較低的快取命中率可能意味著增加 WiredTiger 快取可能會提高效能。如圖11-3所示,當我們增加WiredTiger 快取的大小時,未命中率下降,而處理量上升。這表明,提高WiredTiger 快取的大小可能會提高效能。

@startuml
note
  無法自動轉換的 Plantuml 圖表
  請手動檢查和調整
@enduml

程式碼範例與詳細解說

以下是用於計算WiredTiger 快取命中率的範例程式碼:

var cache = db.serverStatus().wiredTiger.cache;
var missRatio = cache['pages read into cache'] * 100 / cache['pages requested from the cache'];
var hitRatio = 100 - missRatio;
print(hitRatio);

#### 詳細內容解密:

  1. 取得 WiredTiger 快取狀態
    • db.serverStatus().wiredTiger.cache 用於取得當前 WiredTiger 快取的詳細狀態資訊。
  2. 計算未命中率(Miss Ratio)
    • cache['pages read into cache'] 表示從磁碟讀入快取的頁面數量。
    • cache['pages requested from the cache'] 表示從快取請求的頁面總數。
    • 未命中率 = (pages read into cache / pages requested from the cache) * 100,表示需要從磁碟讀取的比例。
  3. 計算命中率(Hit Ratio)
    • hitRatio = 100 - missRatio,表示從快取中直接取得資料的比例。
  4. 輸出結果
    • 使用 print(hitRatio) 輸出計算得到的快取命中率

綜上所述,適當地調整 WiredTiger 快取大小,並監控其效能指標(如快取命中率),對於最佳化 MongoDB 的整體效能至關重要。

MongoDB 記憶體調校:WiredTiger 快取與 Eviction 策略深度解析

MongoDB 的 WiredTiger 儲存引擎採用快取機制來提升效能,但若組態不當,可能會導致效能問題。本文將探討 WiredTiger 快取的工作原理、Eviction 策略以及檢查點(Checkpoints)對效能的影響。

WiredTiger 快取與效能指標

WiredTiger 快取的命中率(Cache Hit Ratio)是衡量 MongoDB 效能的關鍵指標。當工作集(Working Set)能夠完全放入記憶體時,快取命中率會很高,效能最佳。若快取命中率偏低,可能表示需要增加 WiredTiger 快取大小。

程式碼範例:檢查 WiredTiger 快取命中率

db.serverStatus().wiredTiger.cache

內容解密:

  • 此指令用於查詢 WiredTiger 快取的相關資訊。
  • 透過 db.serverStatus().wiredTiger.cache 可以觀察快取的使用情況,包括命中率等關鍵指標。

Eviction 策略與阻塞式 Eviction

當 WiredTiger 快取達到其最大容量時,舊資料需要被清除(Evicted)以騰出空間給新資料。MongoDB 預設會在快取使用率達到一定閾值時開始 Eviction 作業,並嘗試保持一定比例的空閒快取。

若資料已被修改,則 Eviction 前需要將資料寫回磁碟,這會影響效能。MongoDB 透過調整 Eviction 的執行緒數量和觸發條件來最佳化此過程。

程式碼範例:檢查阻塞式 Eviction 發生頻率

var wt = db.serverStatus().wiredTiger;
var blockingEvictRate = wt['thread-yield']['page acquire eviction blocked'] * 100 / wt['cache']['eviction server evicting pages'];
print(blockingEvictRate);

內容解密:

  • 此程式碼計算阻塞式 Eviction 的發生比率。
  • wt['thread-yield']['page acquire eviction blocked'] 表示因 Eviction 而被阻塞的運算元量。
  • wt['cache']['eviction server evicting pages'] 表示 Eviction 伺服器執行的 Eviction 頁面數量。
  • 當阻塞式 Eviction 率較高時,可能需要調整 Eviction 策略,例如增加 Eviction 執行緒數量或提前開始 Eviction。

檢查點(Checkpoints)與效能影響

MongoDB 定期將快取中的修改資料寫回磁碟,這一過程稱為檢查點。檢查點會對效能產生一定影響,因為它涉及大量資料的寫入操作。

調整檢查點相關引數以最佳化效能

  1. 調整 Eviction 相關引數:透過調整 eviction_dirty_triggereviction_dirty_target,可以控制快取中修改資料的比例,從而減少檢查點的負擔。
  2. 增加 Eviction 執行緒:增加 eviction.threads_mineviction.threads_max 可以加速 Eviction 過程,減少檢查點期間需要寫入的資料量。

程式碼範例:調整 WiredTiger Eviction 組態

db.adminCommand({
  setParameter: 1,
  wiredTigerEngineRuntimeConfig: 
    `eviction=(threads_min=4,threads_max=4),
     eviction_dirty_trigger=5,eviction_dirty_target=1,
     eviction_trigger=95,eviction_target=80`
});

內容解密:

  • 此指令用於動態調整 WiredTiger 的 Eviction 組態。
  • eviction=(threads_min=4,threads_max=4) 設定 Eviction 的最小和最大執行緒數量。
  • eviction_dirty_trigger=5eviction_dirty_target=1 控制修改資料的 Eviction 觸發條件。
  • 調整這些引數需要謹慎,因為不當的組態可能會對效能產生負面影響。

MongoDB 記憶體調校

WiredTiger 檢查點(Checkpoint)調校

WiredTiger 儲存引擎使用檢查點機制來確保資料的永續性。預設情況下,每隔一分鐘,WiredTiger 就會將修改過的資料頁寫入磁碟。檢查點的間隔時間可以透過 checkpoint.wait 引數進行調整。如果設定較高的值,則在檢查點發生之前,驅逐處理可能會將大部分割槽塊寫入磁碟,從而降低檢查點的整體影響。然而,延遲檢查點的開銷也可能很大。

檢查點的調校沒有一個固定的答案,有時檢查點的影響可能會與預期相反。例如,當 WiredTiger 快取記憶體較大時,檢查點的開銷可能會更大。這是因為預設的驅逐策略是根據 WiredTiger 快取記憶體的百分比設定的,快取記憶體越大,驅逐處理器就越「懶惰」。

如果您願意進行實驗,可以透過調整檢查點之間的時間和驅逐處理的積極程度來降低檢查點的開銷。例如,可以將檢查點間隔調整為 5 分鐘,增加驅逐執行緒數量,並降低髒區塊驅逐的目標閾值:

db.adminCommand({
  setParameter: 1,
  wiredTigerEngineRuntimeConfig: `
    eviction=(threads_min=10,threads_max=10),
    checkpoint=(wait=300),
    eviction_dirty_trigger=5,
    eviction_dirty_target=1`
});

內容解密:

  • eviction=(threads_min=10,threads_max=10):設定驅逐執行緒的最小和最大值,以提高驅逐效率。
  • checkpoint=(wait=300):將檢查點間隔設定為 5 分鐘(300 秒)。
  • eviction_dirty_trigger=5:設定當髒資料達到 5% 時開始驅逐。
  • eviction_dirty_target=1:設定驅逐目標為 1%,以降低髒資料比例。

WiredTiger Concurrency

在 WiredTiger 快取中讀取和寫入資料需要取得讀取或寫入「票證」。預設情況下,有 128 張票證可用。db.serverStatus() 可以報告 wiredTiger.concurrentTransactions 部分中可用的票證數量:

mongo> db.serverStatus().wiredTiger.concurrentTransactions
{
  "write": {
    "out": 7,
    "available": 121,
    "totalTickets": 128
  },
  "read": {
    "out": 28,
    "available": 100,
    "totalTickets": 128
  }
}

大多數 MongoDB 操作的持續時間很短,因此 128 張票證通常足夠。如果有超過 128 個平行操作,則可能是伺服器或作業系統中的其他瓶頸。可以透過調整 wiredTigerConcurrentReadTransactionswiredTigerConcurrentWriteTransactions 引數來增加票證數量。例如,要將平行讀取器數量增加到 256,可以執行以下命令:

db.getSiblingDB("admin").runCommand({ 
  setParameter: 1, 
  wiredTigerConcurrentReadTransactions: 256 
});

內容解密:

  • wiredTigerConcurrentReadTransactions:控制平行讀取交易的數量。
  • 增加平行讀取器數量可能會耗盡可用的硬體資源,因此需要謹慎調整。

降低應用程式記憶體需求

最佳的調校結果通常是在調整硬體和伺服器組態之前,先調整應用程式設計和工作負載。以下是一些可以幫助降低記憶體需求的方法:

檔案設計

WiredTiger 快取儲存完整的檔案副本,而不僅僅是您感興趣的檔案部分。因此,如果檔案包含大型二進位資料(如高解析度的駕照掃描件),則 WiredTiger 快取需要儲存這些資料,即使您不需要它們。可以採用垂直分割設計模式,將駕照掃描件放在單獨的集合中,以減少記憶體使用。

索引

索引不僅提供快速存取資料的途徑,還有助於降低記憶體需求。當使用完整集合掃描搜尋資料時,所有檔案都會被載入快取中,無論是否符合篩選條件。因此,索引查詢有助於保持快取的有效性。索引還可以減少排序所需的記憶體。

交易

在 MongoDB 4.4 之前,交易會使用資料的快照來確保會話不會讀取未提交的檔案版本。這些快照儲存在 WiredTiger 快取中,減少了可用於其他用途的記憶體。因此,在 MongoDB 4.4 之前,在應用程式中新增交易會增加 WiredTiger 快取所需的記憶體。從 MongoDB 4.4 開始,快照儲存在磁碟上作為「持久歷史」,長交易的記憶體影響變得不那麼重要。