使用大數據時,別忘了關注Linux內存管理器
聲明:我們常常以為,一旦我們(的代碼)出了什么狀況,那肯定是操作系統在作祟,而在99%的情況下,結果都會是別的原因。因此我們會謹慎地作出是操作系統導致了某個問題這樣的假設,除非你遇到了與下面的例子類似的情況。
一切從我們的一個客戶報告了他們的CitusDB集群的性能問題開始。這個客戶設計的集群使得他們的工作數據集合可以放進內存,但是他們的查詢次數顯示他們的查詢已經需要訪問磁盤。這自然會導致查詢效率下降10倍到100倍。
我們開始著手研究這個問題,首先檢查CitusDB的查詢分發機制,然后再檢查機器上安裝的PostgreSQL實例。發現都不是導致該問題出現的原因。接下來的一些發現:
- 客戶的工作數據是某一天的查詢日志。一旦他們看完了某一天的數據,他們會開始查詢下一天的數據。
- 他們的查詢大都是連續的I/O操作,使用索引的情況并不多。
- 某一天的數據會占用一個節點超過60%的內存(但還是大大小于整個可用的內存)。他們實例上沒有別的使用內存的程序。
我們假設,因為每一天的數據可以容易的放進內存,Linux 內存管理器最終會把那一天的數據都放進頁緩存,一旦客戶開始查詢下一天的日志時,新的數據會進入頁緩存,至少,這是一個使用LRU退化策略的簡單緩存(管理器)會做的事情。
但是LRU在用作頁替換策略算法時有兩個缺陷。***,精確的LRU實現在一個系統環境下成本太高了;第二,內存管理器還需要把數據使用的頻率考慮在內,讀入一 個大文件時并不會馬上清除整個cache,因此。Linux使用了比 LRU 更復雜的算法,而這個算法與我們之前描述過的問題協作的效果并不好。
舉例說明。假設你的內核版本號高于2.6.31 ,而你在使用一個內存為68GB的EC2集群,比如你有兩天的點擊流數據。每一天的數據都能超過60%的總的內存,單個來看,都很容易能放進內存。
- $ ls -lh clickstream.csv.*
- -rw-rw-r-- ec2-user ec2-user 42G Nov 25 19:45 clickstream.csv.1
- -rw-rw-r-- ec2-user ec2-user 42G Nov 25 19:47 clickstream.csv.2
現在,我們通過對點擊流文件運行多次 wc 命令來將該天的數據裝進內存。
注意這兩次所用的時間差。
***次我們運行該命令時,Linux內存管理器會將該文件頁放進頁緩存,下一次運行時,會直接從內存里面讀取。
- $ time wc -l clickstream.csv.1
- 336006288 clickstream.csv.1
- real 10m4.575s
- ...
- $ time wc -l clickstream.csv.1
- 336006288 clickstream.csv.1
- real 0m18.858s
現在我們切換到第二天的點擊流文件。我們再多次運行 wc 命令來把文件裝進內存。使用一個類LRU的策略會將***天的數據淘汰,并將第二天的數據裝進內存。不幸的是,在這種情況下,不管你運行多少次,Linux 內存管理器都不會把第二天的數據裝進內存。
- $ time wc -l clickstream.csv.2
- 336027448 clickstream.csv.2
- real 9m50.542s
- $ time wc -l clickstream.csv.2
- 336027448 clickstream.csv.2
- real 9m52.265s
事實上,如果你遇到這種情況,唯一能把第二天的數據裝進內存的辦法就是手動清除掉頁緩存,很明顯,這個做法會比問題帶來的危害更大,但單就我們的這個小測試而言,確實湊效了。
- $ echo 1 | sudo tee /proc/sys/vm/drop_caches
- $ time wc -l clickstream.csv.2
- 336027448 clickstream.csv.2
- real 9m51.906s
- $ time wc -l clickstream.csv.2
- 336027448 clickstream.csv.2
- real 0m17.874s
回到上一步,這兒的問題在于Linux如何管理自己的頁緩存。Linux內存管理器會將文件系統的頁面放到兩種類型的隊列里面。一個隊列(臨近訪問內存隊列,下面簡稱:臨近隊列)放了最近訪問到的頁面。另一個隊列(頻率訪問內存隊列,下面簡稱:頻率隊列)保留了那些被多次訪問到的頁面。
在***的內核版本中,內存管理器將可用的內存公平的分發給兩個隊列,盡量在保護頻繁訪問的頁面和探測最近使用的頁面之間達到一個折衷的平衡。換言之,內核為頻率隊列保留了50%的可用內存。
在之前的例子里,兩個列表一開始都是空的。當***天的數據被引用的時候,會先進入臨近隊列。在第二次被引用的時候,被提升到了頻率隊列。
接下來,當用戶想使用第二天的數據進行工作時,數據文件大于可用內存的50%,但是臨近隊列的空閑空間卻沒那么大。因此,對這個文件的順序掃描就導致了內存的置換震蕩。 第二個文件中的***個文件系統的頁面會先進入臨近隊列,但是一旦臨近隊列空間被占滿了以后,這個頁就被從隊列中置換出來了。因此,第二個文件中沒有兩個頁面會在臨近隊列中停留足夠長的時間,因為他們的引用數一直在遞增。
幸運的是,這個問題只有在當你滿足以上我們列出的三點要素時才會發生。當我們在這里討論的時候,問題正在被修復中。如果感興趣的話,你可以在Linux郵件列表下閱讀更多關于原始問題報告以及提議的一些修復辦法。
對于我們來說,真正利索的是很容易就定位到了問題所在。因為Citus繼承自PostgreSQL,一旦我們發現了這個問題,就可以很快的在Postgres上復現,之后我們向linux郵件組提交了我們的發現,從此社區開始接手。
想發表評論?請加入hacker news的討論。
原文鏈接: Metin Doslu 翻譯: 伯樂在線 - 高磊
譯文鏈接: http://blog.jobbole.com/52898/






















