天天看點

我的記憶體去哪了?

作者:小小怪下士的架構攻略

一、前言

  近幾年開發了一些大型的應用程式,在程式性能調優或者解決一些疑難雜症問題的過程中,遇到最多的還是與記憶體相關的一些問題。例如glibc記憶體配置設定器ptmalloc,google的記憶體配置設定器tcmalloc都存在“記憶體洩漏”,即記憶體不歸還作業系統的問題;ptmalloc記憶體配置設定性能低下的問題;随着系統長時間運作,buffer/cache被某些應用大量使用,幾乎完整占用系統記憶體,導緻其他應用程式記憶體申請失敗等等問題。

  之是以記憶體相關的問題層出不窮,關鍵還是它的地位太重要了。這次還是與記憶體相關,分享的是追蹤buffer/cache占用的記憶體到底被誰(哪些應用程式)偷吃了!

  有關buffer/cache的文章,大多提及的是如何釋放并歸還到系統的方法,但是分析buffer/cache記憶體消耗背後原因的相關文章卻鳳毛麟角。buffer/cache為什麼會增長,它到底被哪些程式使用了,我相信這也是很多同行的疑惑,是以想通過本篇文章分享一些buffer/cache記憶體消耗問題的跟蹤方法,為類似問題的優化和解決提供一些參考。

二、問題描述

  如下圖1和圖2所示,buffer/cache已經占用了46GB的記憶體,達到了整個系統記憶體的37%,這個占比已經非常高了。buffer/cache長期占用不釋放,同時供系統上其它程序使用的可用記憶體幾乎快沒了。

我的記憶體去哪了?
我的記憶體去哪了?

  長此以往,會出現什麼問題呢?最直接的問題就是其他程序沒法玩了,比如大一點的記憶體塊就無法申請。之前分享過一次相關問題的定位,參見連結:https://www.cnblogs.com/t-bar/p/16626951.html,我在這篇文章裡也詳細介紹了buffer/cache的釋放方法,解決了當時的燃眉之急。

  為啥最近又開始與buffer/cache糾纏上了呢?

  “echo 1 > /proc/sys/vm/drop_caches”釋放的是所有cache,這些cache是目前系統上所有程式在運作過程中加載到記憶體的一些檔案資訊,這些資訊被當做緩存用,好處是CPU下次讀取某個檔案時就會比第一次從磁盤讀取快多了。drop_caches執行時會清空所有cache,這樣會帶來一個問題:當某些程式需要讀取之前加載到cache的資訊時就需要重新從磁盤讀取,這就會産生IO等待,或者IO競争,進而拖累程式性能。在某些平台上,我們已經發現有高性能程式因為cache的粗暴清空産生了性能抖動。是以,我們就沒法像以前一樣回避buffer/cache到底被誰使用的問題,并且直接粗暴釋放的政策在某些平台上也就失效了。

  根據上面的描述,我們目前面臨的問題就是:究竟是誰占用了buffer/cache,以及弄清是誰占用後,是否可以規避它對buffer/cache的大量使用。面對這個問題,老闆最近又上火了。

我的記憶體去哪了?

三、buffer/cache使用跟蹤

  開始介紹一下調查buffer/cache占用的跟蹤思路吧。

  1、hcache

  網上有一些文章分享了hcache可以檢視哪些檔案使用了cache,那hache真的可以幫助我們對buffer/cache進行全面調查嗎?我們一起來看看。

  根據前面的問題描述,目前buffer/cache已經占用了46GB的記憶體。先使用hcache檢視一下top100的cache占用,如下圖所示(截取了Cached靠前的一部分)。

我的記憶體去哪了?

  top100,即使top200的Size統計之和,也隻有幾個GB,離46GB相差甚遠,結果說明hcache遺漏了很多cache的使用統計。

  hcache還有一個能力,檢視某個程序目前使用的cache。我們看看clickhouse的cache使用,結果如下圖所示。

我的記憶體去哪了?

  正在運作的clickhouse,居然隻能看到程式可執行檔案本身目前的cache占用,程式運作過程中已打開的cache檔案卻沒統計。不過這裡有個小收獲:程式加載進記憶體後,程式的可執行檔案,依賴的庫檔案使用的記憶體都是在buffer/cache裡。

我的記憶體去哪了?

  從上面的結果發現hcahce有很多缺點,隻能粗略的看到一些可執行程式檔案,或者一些庫檔案使用的cache大小,沒有統計各程式運作态的cache使用,是以對cache占用問題的排查作用非常有限。

  2、top + lsof + fincore

  找了很多資料,除了hcache确實沒有其他方法可以統計目前運作程式消耗的cache大小了,但是hcache本身不可靠。沒有直接的辦法,那就隻有圍魏救趙了,這也是buffer/cache分布情況不便跟蹤調查的原因。

  該從哪裡入手呢?當然是top指令給方向,哪些程式cpu使用率高,且使用了一定的記憶體,那就查它。因為隻有它們才有可能在不斷的使用cache,調查大方向有了。

我的記憶體去哪了?

  下一步呢?buffer/cache的使用肯定跟檔案相關啊,還是那句話:linux一切皆檔案。那有沒有可以實時檢視某個程序目前已打開的檔案方法?lsof指令可以!我們用lsof查一下clickhouse,某時刻,clickhouse打開的檔案如下圖7圖8所示,篇幅太長,圖7隻截取了前面部分。

我的記憶體去哪了?

  圖8隻截取了類型TYPE=REG(REG表示檔案類型為普通,還有DIR為目錄等等等),即截取了clickhouse目前打開,且正在使用的一部分類型為普通的檔案。

我的記憶體去哪了?

  不斷的執行:lsof -p $(pidof clickhouse-server),發現每次檢視到的檔案名都不一樣。好了,這說明clickhouse會在運作過程中不斷的大量打開,讀寫和關閉檔案。嫌疑很重了。

  下一步呢?有沒有辦法可以實時檢視目前這些檔案是不是使用了cache,以及各自使用cache的大小?還真有,fincore可以檢視某個檔案使用的cache大小,連結:https://github.com/david415/linux-ftools。輪子就是齊全啊,要啥有啥。

  指令行:lsof -p $(pidof clickhouse-server) | grep 'REG' | awk '{print $9}' | xargs ./fincore --pages=false --summarize --only-cached *

  截圖較大,點開看會清晰一點。

我的記憶體去哪了?

  fincore統計了指令行執行時,clickhouse目前打開的檔案使用的cache之和為1.2GB左右。到這裡,目前的探索結果與前文提到的問題:究竟是誰占用了buffer/cache,越來越接近了。

  通過top + lsof,發現了一個非常重要的線索,就是clickhouse在目錄/opt/runtime/esch/ch/store下頻繁的打開了很多檔案,那這個目錄下面到底都是一些什麼檔案?有沒有都使用了cache呢?clickhouse是不是cache的消耗大戶呢?解決這些疑惑就産生了另外一個需求,需要一種可以統計指定目錄的cache大小的工具。這次fincore也不行了,fincore有一個緻命弱點,即隻能獲得某個指定檔案的cache占用大小,不能擷取指定目錄使用的cache大小,更别指望統計嵌套目錄的cache大小。是以,是時候該請vmtouch出場了,連結:https://github.com/hoytech/vmtouch,還是這句話:輪子就是齊全啊,要啥有啥。

  3、vmtouch

  vmtouch可以統計指定目錄的cache占用大小,即使是嵌套目錄。

  迫不及待的直接奔主題,看看clickhouse目錄/opt/runtime/esch/ch/store下是什麼,以及使用了多少cache。截取了該目錄下的部分檔案,内容如下圖所示。

我的記憶體去哪了?

  直接統計一下/opt/runtime/esch/ch/store目錄占用的cache規模吧,結果如下圖所示。

我的記憶體去哪了?

  shit,居然吃了我42GB的記憶體啊!地主家的餘糧也不多啊---老闆哭着說。

  激動之餘,我還要确認一下42GB cache的使用者是不是它!如何證明呢,還是使用“echo 1 > /proc/sys/vm/drop_caches”,看看釋放完畢之後,free可用記憶體的大小是否會增長42GB左右。

  執行前的記憶體分布情況:

我的記憶體去哪了?
我的記憶體去哪了?

  執行後的記憶體分布情況:

我的記憶體去哪了?
我的記憶體去哪了?

  執行cache釋放後,free從2GB變為了45GB,擴大了43GB;buffer/cache從46GB變為了3GB,減小了43GB。從cache釋放了clickhouse的42GB+1GB其它程式占用的cache,說明我們環境上,clickhouse就是cache的消耗大戶!老闆沸不沸騰我不知道,反正我是沸騰了。

四、clickhouse cache耗費

  為什麼clickhouse對buffer/cache的消耗如此巨大?在好奇心的驅使下,又開始了新的調查。此時此景想到了一句歌詞:一波還未平息,一波又來侵襲,茫茫人海,狂風暴雨。。。

  1、clickhouse cache耗費原因

  從哪開始調查呢?想起了lsof指令的執行結果,如前文圖9所示的重要線索,clickhouse有大量時間都在打開目錄:/opt/runtime/esch/ch/store/032/03216cf6-357f-477f-bc9b-5eedb07a5d07,判斷該目錄下面肯定有大量消耗cache的檔案。直接進入該目錄,繼續使用vmtouch統計,不出所料,結果如下圖所示,032目錄就吃了24GB記憶體,心好痛啊。

我的記憶體去哪了?

  clickhouse的什麼機制會如此瘋狂的消耗cache呢?我們再看看目錄下有些什麼類型的檔案,截取了部分檔案。

我的記憶體去哪了?

  發現目錄下主要是很多以日期編号開頭的目錄檔案,有純數字組成的,也有帶有merge字元的目錄。随便打開一個5月17日當天的目錄檔案:20230517_563264_565136_5

我的記憶體去哪了?

  20230517_563264_565136_5 目錄就占用了2GB cache,驚不驚喜意不意外?而且上面的所有檔案,都完全加載到了cache中,比如在磁盤中占用743MB的檔案cuid.bin,同樣在cache中占用了742MB。

我的記憶體去哪了?

  查閱clickhouse資料後才發現,數字編号的目錄都是clickhouse的很多分區part,clickhouse服務會根據相關政策自動的在背景合并這些分區。想想,如果在每一次合并分區時,才将上一次的某兩個分區從磁盤進行IO讀取,那将帶來多大的性能開銷。是以,clickhouse的開發者會将上一次的分區合并結果儲存在cache裡,下一次該分區與其它分區再次進行合并時,直接從記憶體裡讀取資料就好了。這就是為什麼clickhouse消耗如此巨大cache的原因。當然,clickhouse對cache的消耗與您目前環境的資料存儲規模呈正相關。

  再來看一個問題,那昨天的所有分區,加載的資料還會保留在cache裡嗎?

  我找了一個昨天的分區,可以發現昨天的分區目錄裡的檔案是不再占用cache的。上一天的分區,clickhouse認為是合并完成的分區,已經不需要再進行合并了,自然就clear了cache的占用,開發者也是想到了的。

我的記憶體去哪了?

  2、clickhouse cache耗費調整

  可是當天分區消耗的cache,以及merge過程中使用的cache就讓我沒法玩了,尤其是在clickhouse服務并未獨立部署的場景。那clickhouse自身可以支援改變這一機制嗎?帶着疑問又開始了一探究竟,完全沒法停下來。

  後來在clickhouse社群找到了一個可以節省cache使用的相關問題。連結:https://github.com/ClickHouse/ClickHouse/issues/1566,有配置min_part_size_for_direct_merge,意思是超過min_part_size時,啟用direct_io。也就是此時clickhouse會通過direct_io的方式讀寫merge的源檔案和目的檔案,而不是使用cache緩存,通過這種方式減少cache的使用。

我的記憶體去哪了?

  我們的clickhouse版本比較高,看社群記錄,clickhouse官方将之前的min_part_size_for_direct_merge改成為min_merge_bytes_to_use_direct_io,Minimal amount of bytes to enable O_DIRECT in merge (0 - disabled)。預設超過10GB時會使用direct_io的方式進行merge。

我的記憶體去哪了?

  那我将min_merge_bytes_to_use_direct_io設定足夠小,甚至是1byte,是不是就可以完全避免對cache的使用了?答案是否定的,原因是:min_merge_bytes_to_use_direct_io隻是讀寫表資料時使用了direct_io,替換了常用的buffer_io。也就是說隻是在資料傳輸過程不使用cache,節省的是這個環節的cache記憶體消耗。merge完成後,先通過direct_io将資料寫入到磁盤,同時會繼續使用cache緩存merge完成後的資料,友善為下一次與其它分區進行快速merge做準備。因為每次merge都是merge舊資料與新資料,是以新合成的分區所使用的cache隻會比merge前的更大。direct_io與buffer_io的差別如下圖所示。

我的記憶體去哪了?

  需要注意的是,設定min_merge_bytes_to_use_direct_io還有一個副作用,當發生merge行為時會導緻磁盤IO急劇拉高。因為direct_io是對磁盤進行直接操作,這種IO方式與buffer_io(使用了page cache做緩沖層)相比,帶給磁盤的沖擊更大。但是如果鈔票比較多,可以做磁盤raid,或者增加了SSD,磁盤能夠扛住direct_io沖擊的同時,還能支援前端的絲滑查詢,那就另當别論啦!

我的記憶體去哪了?

  另外還設定過相關參數:max_bytes_to_merge_at_max_space_in_pool,用處不大,就不繼續介紹了,讀者可以自行驗證。隻能說clickhouse目前沒有提供待merge分區檔案所占用cache的清理機制。

五、cache清理

  一頓操作猛如虎,定睛一看原地杵。clickhouse自身無法限制cahce消耗;“echo 1 > /proc/sys/vm/drop_caches”又太粗暴,會清理掉其它程序加載到cache中的内容。隻想搞掉clickhouse占用的大量cache,該怎麼辦?

  有時候你不得不相信車到山前必有路,船到橋頭自然直。再次請出vmtouch!

  vmtouch的help中有一個“-e”的選項,即“evict pages from memory”,顧名思義将page cache從記憶體中驅逐出去。既然vmtouch可以統計指定檔案或目錄占用的cache,那自然就可以實作對指定檔案或目錄的cahce清理!

我的記憶體去哪了?

  先來看看執行效果。執行前有記憶體分布,以及某個目錄下cache的使用情況:

我的記憶體去哪了?
我的記憶體去哪了?

  執行:vmtouch -e ./* 後,記憶體分布如下圖28所示。驚喜嗎?剛好減少了某個目錄下占用的30GB cache,同時free記憶體增加了30GB,實作了對指定目錄cache消耗的定點清理!

我的記憶體去哪了?

  很好奇vmtouch實作指定目錄記憶體清理的方法,去看了看源碼,簡單貼兩張圖就大緻明白了。

  1、傳遞指定path;

  2、如果該path下無目錄,則通過vmtouch_file函數執行cache的釋放操作;如果該目錄下存在其他檔案(包括子目錄),則周遊該目錄下的所有檔案,并通過vmtouch_crawl實作遞歸調用,回到第1步。

我的記憶體去哪了?

  那vmtouch_file函數如何實作cache的釋放操作呢?答案就是通過系統調用:posix_fadvise,并使用POSIX_FADV_DONTNEED宏。定義如下:

1 int posix_fadvise(int fd, off_t offset, off_t len, int advice);
2  
3 advice:
4 POSIX_FADV_DONTNEED,指定的資料在未來的一段時間内不會被通路,丢棄page cache中的資料           

  當advice為POSIX_FADV_DONTNEED時,核心會先将髒頁刷盤,再清除相關page cache,進而達到cache釋放的目的!

我的記憶體去哪了?

  但是查閱資料,發現 posix_fadvise 實作髒頁刷盤使用的參數是:WB_SYNC_NONE,即posix_fadvise 不會同步等待其它程序發起的髒頁刷盤行為,這可能會帶來一個問題:不能完全釋放指定路徑下的cache,是以可以在執行cache釋放前先使用 fsync,将所有的髒頁進行回寫,完成後再調用 posix_fadvise,實作指定path下所有cache的釋放。如果不關心髒頁使用的cache是否可以被全被釋放,那直接使用posix_fadvise 就好了。

  寫到這裡,可能有些讀者會産生一個問題,清理cache就真的很好嗎?答案是肯定的。為什麼這麼說呢,我想可以概括為以下兩點:

  一是有些服務使用完cache後,這部分cache再也不會被通路,這一類cache消耗當然是需要幹掉的;

  二是類似我們環境clickhouse這一類應用,它們會再次使用之前使用過的cache。這種情形确實不好辦,但是如果不辦它,最後就是大家都别玩了,魚和熊掌不可兼得。是以,對這一類應用,定期清理cache是必要的,通過一個折中的辦法讓大家共存下來。對依賴cache這一類應用,cache清理後就隻有辛苦下磁盤了(人在家中坐,鍋從天上來)。

六、結語

  摸索cache的過程曲折且漫長,還好最後找到了一個大家都可以接受的辦法。文章篇幅比較長,簡單總結一下全文吧,概括起來可歸納為以下四點:

  1、驗證了 hcache 無法統計cache的全面消耗情況,推薦了一種通過 lsof+fincore 探測程序中活躍目錄以及檔案的方法,并以此作為cache消耗的關鍵調查線索。這種方法适用于多種場景下的cache消耗調查,不僅僅是clickhosue。

  2、通過vmtouch可以統計出某個檔案、目錄,甚至是嵌套目錄下各檔案真正使用cache的大小,進而明确cache消耗的分布情況。

  3、分析了clickhouse大量消耗cache的原因,探索了clickhouse自身是否具備減少cache使用的能力和機制。

  4、提供了清理各檔案,或者各指定目錄所占用cache的通用方法,屬于定點清除的騷操作。

  技術是不斷實踐積累的,在此分享出來與大家一起共勉!

  如果文章對你有些許幫助,還請各位技術愛好者登入點贊呀,非常感謝!

原文連結:https://www.cnblogs.com/t-bar/p/17359545.html