MongoDB 效能調校的關鍵在於深入瞭解伺服器狀態。除了分析個別查詢和使用資料函式庫分析器之外,db.serverStatus() 命令提供伺服器層級的豐富資訊,涵蓋操作計數、佇列、索引使用、連線數、磁碟 I/O 和記憶體用量等導向。然而,db.serverStatus() 輸出的資訊量龐大,需要進一步分析和解讀才能有效識別效能瓶頸。利用 mongoTuning 工具包中的 keyServerStats 函式,可以簡化資料取樣和分析流程,快速取得關鍵效能指標,例如每秒查詢次數、網路吞吐量等。此外,MongoDB Compass 圖形化介面提供更直觀的效能監控和分析方式,便於快速診斷問題。除了資料函式庫本身,作業系統資源的監控也至關重要,需要關注 CPU 負載、記憶體使用和磁碟 I/O 等指標,避免系統資源成為效能瓶頸。

MongoDB 伺服器狀態監控與效能調校

在進行 MongoDB 的效能調校時,瞭解伺服器的整體運作狀況是至關重要的。除了分析個別查詢的執行計畫和使用資料函式庫分析器來檢查特定資料函式庫上的查詢外,我們還可以透過 db.serverStatus() 命令來取得伺服器層級的資訊,包括操作計數器、佇列資訊、索引使用情況、連線數、磁碟 I/O 和記憶體使用率等。

使用 db.serverStatus() 命令

db.serverStatus() 命令提供了大量有關 MongoDB 伺服器活動的指標。這個命令對於識別效能問題或深入瞭解影回應用程式效能的其他因素非常有幫助。例如,當你無法理解為什麼某個查詢執行得這麼慢時,檢查 CPU 和記憶體使用率可能會提供重要的線索。

指令範例

mongo> db.serverStatus()
{
  "host" : "Mike-MBP-3.modem",
  "version" : "4.2.2",
  "process" : "mongod",
  "pid" : NumberLong(3750),
  "uptime" : 474921,
  ...
}

內容解密:

  • db.serverStatus() 命令輸出了大量的伺服器狀態資訊。
  • 包含伺服器的主機名、版本、行程 ID、執行時間等基本資訊。
  • 其中還包含了斷言、操作計數器、鎖定資訊、網路流量等多個類別的詳細資訊。

提取特定資訊

由於 db.serverStatus() 輸出內容龐大,通常我們會提取特定的值或將資料彙總成更易於解析的格式。例如,若要取得各種高階命令的執行次數,可以直接存取 opcounters 欄位。

程式碼範例

mongo> db.serverStatus().opcounters
{
  "insert" : NumberLong(3),
  "query" : NumberLong(1148),
  "update" : NumberLong(15),
  "delete" : NumberLong(11),
  "getmore" : NumberLong(0),
  "command" : NumberLong(2584)
}

內容解密:

  • opcounters 中包含了插入、查詢、更新、刪除等操作的計數。
  • 這有助於快速瞭解伺服器上執行的操作型別和頻率。

常用的 db.serverStatus() 資訊類別

以下是一些常用的頂層類別:

  • connections:有關伺服器連線的統計資訊。
  • opcounters:命令執行的總數。
  • locks:內部鎖定的計數器。
  • network:進出伺服器的網路流量摘要。
  • opLatencies:讀寫命令和事務的延遲時間。
  • wiredTiger:WiredTiger 儲存引擎的統計資訊。
  • mem:記憶體使用率。
  • transactions:事務統計資訊。
  • metrics:雜項指標,包括彙總階段和特定命令的計數。

改進方法

雖然 db.serverStatus() 提供了豐富的資訊,但直接使用它仍存在兩個問題:一是它提供的計數器資訊不一定能反映當前伺服器的狀況,二是需要事先知道要查詢哪些指標。

解決這兩個問題的方法之一是透過在特定時間間隔內取樣兩次資料,並計算兩次取樣之間的差值,從而得出速率指標。

範例程式碼

mongo> var sample = function() {
... var sampleOne = db.serverStatus().opcounters.query;
... sleep(10000); // 等待10秒
... var sampleTwo = db.serverStatus().opcounters.query;
... var delta = sampleTwo - sampleOne;
... print(`在取樣期間內有 ${delta} 次查詢操作。`);
... }
mongo> sample()
在取樣期間內有 6 次查詢操作

內容解密:

  • 定義了一個名為 sample 的函式,用於計算10秒內查詢操作的次數。
  • 首先記錄當前的查詢操作次數,然後等待10秒,再次記錄查詢操作次數。
  • 計算兩次記錄之間的差值,即為10秒內的查詢操作次數。

使用 mongoTuning.keyServerStats

為了更方便地取得關鍵效能指標,mongoTuning 包中提供了一個名為 keyServerStats 的函式,可以在指定的時間間隔內對 serverStatus 進行兩次取樣,並計算出各種速率指標。

範例輸出

rs1:PRIMARY> mongoTuning.keyServerStats(60000)
{
  "netKBInPS" : "743.4947",
  "netKBOutPS" : 946.0005533854167,
  "intervalSeconds" : 60,
  "queryPS" : "2392.2833",
  ...
}

內容解密:

  • 輸出了每秒的網路輸入/輸出千位元組數、查詢操作次數等關鍵效能指標。
  • 時間間隔設為60秒。

MongoDB 效能調校工具與技術

在進行 MongoDB 效能調校時,瞭解目前的系統狀態與操作狀況至關重要。本篇文章將介紹多個重要的工具與技術,幫助您監控與最佳化 MongoDB 的效能。

使用 db.serverStatus() 監控 MongoDB 狀態

db.serverStatus() 命令提供了 MongoDB 伺服器的詳細狀態資訊,包括操作延遲、連線數、記憶體使用情況等。雖然輸出的資訊量龐大,但大多數情況下,我們只需要關注其中幾個關鍵指標。

mongo> db.serverStatus()
{
  "readLatencyMs" : "0.4803",
  "writeLatencyMs" : "7.0247",
  "cmdLatencyMs" : "0.0255",
  // 其他資訊...
}

內容解密:

  • readLatencyMswriteLatencyMscmdLatencyMs 分別代表讀取、寫入和命令操作的平均延遲時間,單位為毫秒。這些指標對於評估 MongoDB 的效能至關重要。
  • 使用 mongoTuning 這樣的輔助工具,可以幫助簡化對這些指標的分析。

檢查目前操作:db.currentOp()

db.currentOp() 命令用於檢查目前在 MongoDB 上執行的操作,包括背景操作。這個命令對於識別耗時或資源密集的操作非常有用。

mongo> db.currentOp().inprog.length
7
mongo> db.currentOp().inprog[0]
{
  "type" : "op",
  "host" : "Centos8:27017",
  "desc" : "conn557",
  // 其他資訊...
}

內容解密:

  • inprog 陣列包含了目前正在執行的操作列表。
  • microsecs_running 表示操作已執行的時間(微秒)。
  • ns 代表操作的名稱空間(資料函式庫和集合)。
  • op 表示操作型別,而 command 則顯示了具體的操作命令。
  • planSummary 簡要描述了 MongoDB 的執行計劃。

篩選特定操作

您可以透過傳遞篩選條件給 db.currentOp() 命令來只顯示特定的操作,例如篩選特定名稱空間或操作型別。

> db.currentOp({ns: "enron.messages"})
> db.currentOp({ns: "enron.messages", op: "getmore"})

內容解密:

  • 可以使用 {ns: "資料函式庫名稱.集合名稱"} 篩選特定集合的操作。
  • 結合 {op: "操作型別"} 可以進一步篩選特定型別的操作,如查詢、插入或更新。

終止問題操作:db.killOp()

一旦識別出有問題的操作,可以使用 db.killOp() 命令終止它。

mongo> db.currentOP({$ownOps: true}).inprog[0].opid
69035
mongo> db.killOp(69035)
{ "info" : "attempting to kill op", "ok" : 1 }

內容解密:

  • 使用 db.killOp() 時需要提供操作的 opid
  • 終止操作後,可以再次使用 db.currentOp() 驗證該操作是否已被終止。

作業系統監控

除了監控 MongoDB 本身的狀態外,還需要關注託管 MongoDB 的作業系統資源使用情況,以確保系統資源充足,不會成為效能瓶頸。

MongoDB效能調校工具與技術

在第2章中,我們瞭解到MongoDB叢集可由多個Mongo程式實作,並且這些程式可能分佈在多台機器上。此外,MongoDB程式可能與其他程式和工作負載共用機器資源。在容器化或虛擬化主機中執行MongoDB時尤其如此。

作業系統監控的重要性

作業系統監控是一個龐大的主題,我們在此只能觸及表面。然而,以下考量適用於所有作業系統和型別:

  • 為了有效利用CPU資源,CPU利用率接近100%是完全可以的。但是,CPU執行佇列(即等待CPU變為可用的程式數量)應保持盡可能低。我們希望MongoDB能夠在需要時獲得CPU資源。
  • MongoDB程式(尤其是WiredTiger快取)應完全包含在真實系統記憶體中。如果MongoDB程式或記憶體被“交換”到磁碟,效能將迅速下降。
  • 磁碟服務時間應保持在所涉及的磁碟裝置的預期範圍內。預期的服務時間在不同磁碟之間有所不同,特別是在固態硬碟硬碟和較舊的磁性硬碟之間。但是,磁碟回應時間通常應低於5毫秒。

監控工具

大多數嚴肅的MongoDB叢集都在Linux作業系統上執行。在Linux上,命令列工具vmstatiostat可以檢索高階統計資訊。在Microsoft Windows上,圖形任務管理器和資源監視器工具可以執行一些相同的功能。

無論採用何種方式,請確保在檢查伺服器統計資訊時保持對作業系統資源利用率的關注。例如,增加WiredTiger快取大小可能根據db.serverStatus()的檢查結果是合理的,但如果沒有足夠的空閒記憶體來支援這種增加,那麼您實際上可能會看到效能下降,因為您增加了快取大小。

MongoDB Compass

瞭解如何僅使用MongoDB shell進行調校是一項重要的技能。但這並不是唯一的方法。

MongoDB Compass簡介

MongoDB Compass是MongoDB的官方GUI(圖形使用者介面),它封裝了我們在此檢視的許多命令,以及一些更進階的功能。它以易於使用的介面呈現這些工具。MongoDB Compass是免費的,並且是在shell之外的一個方便的工具。

然而,重要的是要記住,您離核心工具(即我們在前文中學習的資料函式庫方法)越遠,您就越不可能瞭解引擎蓋下發生的事情。我們不會在這本文中遍歷Compass的每個部分,但我們將簡要檢視它如何包裝和顯示我們在本章中學習的其他工具。您可以從www.mongodb.com/products/compass下載MongoDB Compass。

使用MongoDB Compass

正如我們之前所見,MongoDB Compass可以顯示圖形化的explain計劃(見圖3-1)。

MongoDB Compass還允許您更輕鬆地解釋我們從db.serverStatus()檢索到的伺服器資訊。在Compass中,當您選擇一個叢集時,您可以簡單地切換到視窗頂部的“效能”標籤。Compass將自動開始收集和繪製有關您的伺服器的關鍵資訊。還將顯示有關當前操作的資訊。圖3-3顯示了MongoDB Compass效能標籤。

第二部分:應用程式和資料函式庫設計

MongoDB 資料模型設計:效能調校的關鍵

在資料函式庫的世界中,資料模型(schema)定義了資料的內部結構與組織方式。對於像 MySQL 或 Postgres 這樣的關係型資料函式庫來說,資料模型是以表格和欄位的形式來實作的。MongoDB 雖然常被描述為無模式(schema-free)的資料函式庫,但這其實是一種誤導。事實上,MongoDB 支援的是彈性模式(flexible schemas),也就是說雖然 MongoDB 不強制要求特定的檔案結構,但所有的 MongoDB 應用程式都會實作某種形式的檔案模型。

資料模型設計的重要性

MongoDB 的效能極限在很大程度上取決於應用程式所實作的檔案模型。應用程式需要檢索或處理的資訊量主要取決於這些資訊在多個檔案中的分佈方式。此外,檔案的大小也會決定 MongoDB 可以在記憶體中快取多少檔案。這些以及其他多種權衡因素將決定資料函式庫需要做多少實體工作來滿足資料函式庫請求。

儘管 MongoDB 沒有像 SQL 的 ALTER TABLE 那樣昂貴且耗時的陳述式,但一旦檔案模型被建立並佈署到生產環境後,要對其進行根本性的更改仍然非常困難。因此,選擇正確的資料模型是應用程式設計中的一個關鍵早期任務。

MongoDB 資料模型設計的指導原則

  1. 避免使用 Join:MongoDB 支援使用聚合框架進行簡單的 Join 操作,但與關係型資料函式庫不同,這些操作應該是例外而非常規。聚合框架中的 Join 操作比較笨重,通常需要在應用程式碼中進行資料的合併。一般來說,我們會盡量確保關鍵查詢可以在單一集合中找到所需的所有資料。

  2. 管理冗餘:透過將相關資料封裝在單一檔案中,我們會創造出冗餘的問題——同一個資料元素可能在資料函式庫的多個地方出現。例如,在產品集合和訂單集合中,訂單詳情中可能會包含產品名稱。如果需要更改產品名稱,就需要在多個地方進行更改,這使得更新操作可能變得非常耗時。

  3. 注意 16MB 的限制:MongoDB 對單個檔案的大小有 16MB 的限制。我們需要確保不會嵌入過多的資訊而超出這個限制。

  4. 維護一致性:MongoDB 支援事務(transactions),但它們需要特殊的程式設計並且有明顯的限制。如果希望原子性地更新多組資訊,將這些資料元素包含在單一檔案中是有益的。

  5. 監控記憶體使用:我們希望確保大多數對 MongoDB 檔案的操作都發生在記憶體中。然而,如果透過嵌入大量資訊使檔案變得非常大,就會減少可以放入記憶體的檔案數量,可能會增加 IO 操作。因此,在可能的情況下,我們會盡量保持檔案的大小。

嵌入 vs. 參照

MongoDB 的資料模型設計模式有很多種,但基本上都涉及以下兩種方法的變化:

  • 將所有相關資料嵌入到單一檔案中。
  • 使用指向其他集合中的資料的指標來連結集合。這大致相當於使用關係型資料函式庫的第三正規化形式模型。

範例研究

讓我們以經典的「訂單」模型作為案例研究。訂單模型包括訂單、建立訂單的客戶詳情以及構成訂單的產品。在關係型資料函式庫中,我們可能會將這個模型設計成如圖 4-1 所示的形式。

如果我們僅使用參照(linking)正規化來建模這個 schema,我們可能會為四個邏輯實體中的每一個都建立一個集合。它們可能看起來像這樣:

mongo> db.customers.findOne();
{
  "_id" : 3,
  "first_name" : "Danyette",
  "last_name" : "Flahy",
  "email" : "dflahy2@networksolutions.com",
  "Street" : "70845 Sullivan Center",
  "City" : "Torrance",
  "DOB" : ISODate("1967-09-28T04:42:22Z")
}

mongo> db.orders.findOne();

內容解密:

上述範例展示了在 MongoDB 中如何查詢客戶和訂單的詳細資訊。這裡使用了兩個不同的集合:customersorders。每個檔案都包含了相關實體的詳細資訊。在實際應用中,我們需要根據具體的需求和查詢模式來決定是使用嵌入還是參照的方式來組織我們的資料。