使用大資料時,別忘了關注Linux記憶體管理器

高磊發表於2013-12-24

宣告:我們常常以為,一旦我們(的程式碼)出了什麼狀況,那肯定是作業系統在作祟,而在99%的情況下,結果都會是別的原因。因此我們會謹慎地作出是作業系統導致了某個問題這樣的假設,除非你遇到了與下面的例子類似的情況。

一切從我們的一個客戶報告了他們的CitusDB叢集的效能問題開始。這個客戶設計的叢集使得他們的工作資料集合可以放進記憶體,但是他們的查詢次數顯示他們的查詢已經需要訪問磁碟。這自然會導致查詢效率下降10倍到100倍。

我們開始著手研究這個問題,首先檢查CitusDB的查詢分發機制,然後再檢查機器上安裝的PostgreSQL例項。發現都不是導致該問題出現的原因。接下來的一些發現:

  1. 客戶的工作資料是某一天的查詢日誌。一旦他們看完了某一天的資料,他們會開始查詢下一天的資料。
  2. 他們的查詢大都是連續的I/O操作,使用索引的情況並不多。
  3. 某一天的資料會佔用一個節點超過60%的記憶體(但還是大大小於整個可用的記憶體)。他們例項上沒有別的使用記憶體的程式。

我們假設,因為每一天的資料可以容易的放進記憶體,Linux 記憶體管理器最終會把那一天的資料都放進頁快取,一旦客戶開始查詢下一天的日誌時,新的資料會進入頁快取,至少,這是一個使用LRU退化策略的簡單快取(管理器)會做的事情。

但是LRU在用作頁替換策略演算法時有兩個缺陷。第一,精確的LRU實現在一個系統環境下成本太高了;第二,記憶體管理器還需要把資料使用的頻率考慮在內,讀入一 個大檔案時並不會馬上清除整個cache,因此。Linux使用了比 LRU 更復雜的演算法,而這個演算法與我們之前描述過的問題協作的效果並不好。

舉例說明。假設你的核心版本號高於2.6.31 ,而你在使用一個記憶體為68GB的EC2叢集,比如你有兩天的點選流資料。每一天的資料都能超過60%的總的記憶體,單個來看,都很容易能放進記憶體。

現在,我們通過對點選流檔案執行多次 wc 命令來將該天的資料裝進記憶體。

注意這兩次所用的時間差。

第一次我們執行該命令時,Linux記憶體管理器會將該檔案頁放進頁快取,下一次執行時,會直接從記憶體裡面讀取。

現在我們切換到第二天的點選流檔案。我們再多次執行 wc 命令來把檔案裝進記憶體。使用一個類LRU的策略會將第一天的資料淘汰,並將第二天的資料裝進記憶體。不幸的是,在這種情況下,不管你執行多少次,Linux 記憶體管理器都不會把第二天的資料裝進記憶體。

事實上,如果你遇到這種情況,唯一能把第二天的資料裝進記憶體的辦法就是手動清除掉頁快取,很明顯,這個做法會比問題帶來的危害更大,但單就我們的這個小測試而言,確實湊效了。

回到上一步,這兒的問題在於Linux如何管理自己的頁快取。Linux記憶體管理器會將檔案系統的頁面放到兩種型別的佇列裡面。一個佇列(臨近訪問記憶體佇列,下面簡稱:臨近佇列)放了最近訪問到的頁面。另一個佇列(頻率訪問記憶體佇列,下面簡稱:頻率佇列)保留了那些被多次訪問到的頁面。

在最新的核心版本中,記憶體管理器將可用的記憶體公平的分發給兩個佇列,儘量在保護頻繁訪問的頁面和探測最近使用的頁面之間達到一個折衷的平衡。換言之,核心為頻率佇列保留了50%的可用記憶體。

在之前的例子裡,兩個列表一開始都是空的。當第一天的資料被引用的時候,會先進入臨近佇列。在第二次被引用的時候,被提升到了頻率佇列。

接下來,當使用者想使用第二天的資料進行工作時,資料檔案大於可用記憶體的50%,但是臨近佇列的空閒空間卻沒那麼大。因此,對這個檔案的順序掃描就導致了記憶體的置換震盪。 第二個檔案中的第一個檔案系統的頁面會先進入臨近佇列,但是一旦臨近佇列空間被佔滿了以後,這個頁就被從佇列中置換出來了。因此,第二個檔案中沒有兩個頁面會在臨近佇列中停留足夠長的時間,因為他們的引用數一直在遞增。

幸運的是,這個問題只有在當你滿足以上我們列出的三點要素時才會發生。當我們在這裡討論的時候,問題正在被修復中。如果感興趣的話,你可以在Linux郵件列表下閱讀更多關於原始問題報告以及提議的一些修復辦法

對於我們來說,真正利索的是很容易就定位到了問題所在。因為Citus繼承自PostgreSQL,一旦我們發現了這個問題,就可以很快的在Postgres上覆現,之後我們向linux郵件組提交了我們的發現,從此社群開始接手。

想發表評論?請加入hacker news的討論。

相關文章