用過mongodb 3.0之後版本的同學應該都比較熟悉wiredtiger的cache evict問題。
<code>連續好幾個版本在cache 淘汰算法上設計都有些小問題,現象總結起來就是寫入hang住</code>。本文使用的是mongodb v3.2.9下wiredtiger-2.8.1(現在wt官方主推v2.9.0版本, mongodb v3.4之後會使用這個版本,雲mongodb現在可以使用v3.2.9,後續我們會很快支援)。wiredtiger逐漸并入mongodb的分之管理,github可以看到如下:

我們在服務内部使用者的時候,發現在write heavy場景下,通過mongostat發現used%居高不下,寫入也會抖動的比較厲害,磁盤io偶爾出現uitl 100%或者非常空閑的奇怪現象,抓取了mongo的stack發現異常的線程和cache evict相關,緊急情況下收集了部分資料,将mongodb更新到v3.2.10,問題解決。于是,我們線上下環境進行了複現,收集了profiling資料。
參數選項
含義
cache_size
cache的總大小
eviction_trigger
百分比, 如果cache記憶體使用量超過此百分比,則觸發cache evict的淘汰
eviction_target
百分比,如果觸發了evict淘汰,則需要将cache記憶體使用量降至此百分比之下,才停止evict
eviction_dirty_trigger
百分比, 如果cache中dirty page記憶體使用量超過此百分比,則觸發cache evict的淘汰
eviction_dirty_target
百分比,如果觸發了evict淘汰,則需要将cache中dirty page記憶體使用量降至此百分比之下,才停止evict
evict.threads_max
最大并發的evict線程數
evict.threads_min
最少并發的evict線程數
mongodb預設設定:
<code>threads_max = 4, eviction_target == eviction_dirty_target == 80%</code>
<code>eviction_trigger == eviction_dirty_trigger == 95%,cache_size使用者設定</code>
線上下環境我們讓cache_size盡可能的小,分别設定了<code>2g</code>和<code>10mb</code>,通過sysbench和ycsb在insert heavy情況下複現并遇到了如下兩個問題。
<code>壓測程式qps陡降到100以下,mongostat發現寫入幾乎完全hang住,cache used 100%左右徘徊</code>
堆棧采樣顯示<code>__tree_walk_internal</code>占據絕大部分cpu,這個函數是btree 周遊page所用。cache子產品的evict_server在尋找可以淘汰的dirty/clean page時會用這個函數依次周遊dirty/clean page,檢視是否可以将page進行淘汰,evict_server每檢查一次page就會觸發__tree_walk_internal,<code>到這裡我們猜想cache evict是在大量的周遊page</code>,導緻cpu飙高。
同樣一個測試資料集,我們使用cachesize=10mb(最初的目的是讓問題盡快複現),偶發出現寫入hang,但cpu都是idle。并且mongodb基本是不可用的狀态,·cache used甚至超過100%·
此時獲得的堆棧采樣在·__wt_sleep()·函數采樣比例較高。但由于wiredtiger自選地方比較多,__wt_sleep()經常出現在spin超過一定次數後發生yield,還好有strace,發現sleep()的調用的入參十分有規律,是n + 1000的sleep方式,通過追代碼發現如下
這是當evict_server找到一些page後,<code>但是evict_worker不能進行evict操作</code>,也就是之前統計的<code>pages_evicted</code>等于現在的<code>cache->pages_evict</code>(說明evict的page數量是0),會主動的休眠一下。
<code>這裡官方的想法應該是現在evict_worker已經很忙了,不能再evict(比如磁盤io很大),這時候應該通過sleep放慢evict_server查找髒頁的速度(這個假設不一定是成立的,後面會講到</code>)。
至此,基本的trace工具能搜集到的都差不多收集完了,其實還有systemtap這個神器還沒用,打點的功能真是沒的說。細心的同學可能發現上圖中紅色方形中的wt_stat相關的内部采集點,沒錯wt内部提供了這樣的機制,通過點計數方profiling内部的邏輯。
mongodb本身支援了wt的profiling參數,通過--<code>wiredtigerstatisticslogdelaysecs</code>可定期将統計資訊寫入統計日志(預設0不開啟)。我們設定5s一次的采集,在dbpath下會生成很多wiredtigerstat.xx.yy的檔案。然後我們通過内置的<code>wtstats.py</code>腳本來生成可讀的html
打開之後可以看到cache、cursor、blockmanager、log、transaction等統計資訊
此圖可以清晰的看到cache evict的内部采集名額,發現在大量出現下降的400這個地方,很多統計都是陡降(pages_queued要淘汰的page入隊數量、pages_evict淘汰數量、pages_reconcile葉子寫盤數量),增長的三個名額分别是:
<code>pages walked for eviction</code>,evict時周遊的page數量
<code>pages seen by eviction</code>,evict時檢查的page數量
<code>pages currently held in cache</code>,cache中page的數量
那麼,問題就顯而易見了,pages在不斷的被evict_server看到,周遊的速度還是很快的,但是有如下問題:
通路的page不能被evict,是以不能如隊列,是以cpu會飙升,但找不到合适的page,<code>這間接導緻了上面說的在不能evict時候會sleep的問題</code>。
不能被evict_worker寫入磁盤,導緻evict失敗。
問題和現象已經很明朗了,下面就是根據代碼找到問題所在,這裡需要gdb和perf進行調試和采樣,需要帶上wiredtiger的debug symbol。為了防止編譯mongodb代碼(編譯時間太痛苦,20分鐘以上)。
這裡使用<code>--use-system-wiredtiger</code>來指定可以使用自己編譯的wiredtiger,這個技巧也可用來測試wt的相容性和開發的debug用。
經過debug發現3處存在問題的地方:
evict_server通過__evict_walk_file周遊btree檔案的page時,會給cache->evict_walk_lock加鎖。同樣在checkpoint_server中,對evict_walk_lock也會加鎖<code>導緻evict和checkpoint操作是互斥的</code>
這帶來的效果就是checkpoint如果慢,則evict也會滞後,導緻cache增長。這樣的問題在測試時候出現過,表現為寫入hang滞後,cache used一直漲,但過一段時間後,寫入可以恢複的場景。
page_seen是周遊btree的page數量,從第二節中給出的曲線圖可以看出這個值一直在快速增長
可以evict的page陡降說明上述代碼中<code>__wt_page_can_evict()傳回false</code>,或者之前的continue,說明page不能淘汰。這裡分析了下原因可能有3個:
看到的page很多都是internal_page也就是索引頁
或者cache狀态是wt_evict_pass_would_block幾近滿了并且memory_footprint很大
發現btree在做checkpoint,并且陷入了之前說的sleep問題(此時并不是evict太慢,而是沒有page可以evict,但是evict_server還是會sleep)
可以看到__wt_evict(這個函數是evict_worker淘汰一個page時調用),傳回的ret值是16也就是ebusy,在深入往下追隻有兩個地方會傳回ebusy:
在看設定wt_evict_update_restore的地方
由于未設定wt_evict_update_restore而導緻__wt_evict不能刷page
分析到這裡,總結一下原因
checkpoint影響cache evict,導緻evict延時,并且可能導緻page不能當做evict_page進行淘汰
evict_server由于__wt_evict失敗(由于wt_readgen_oldest标記為mark),evict_worker不能刷page,這時由于evict_server的退讓機制(上面的sleep機制) 導緻evict問題被放大,越來越慢。
mongodb官方針對cache問題也提了幾個issue,在v3.2.10和v3.4.0中都進不了不少。涉及的issue如下:
wt-2924 ensure we are doing eviction when threads are waiting for it
wt-2545 investigate eviction tree walk
wt-2639 add tree walk optimization to skip subtrees not in memory
wt-2702 under high thread load, wiredtiger exceeds cache size
wt-2664 change eviction so any eviction thread can find candidates
可以增大cachesize,讓evict更少,以此來減少上面沖突的機率,也可以拉平evict的差距。
減小evict_triger、evict_target的設定,更早的觸發evict行為,這樣每次evict執行時間會縮短
修改源碼,臨界淘汰時候之淘汰clean page(這樣減少磁盤io),或者禁用sleep退讓模式
用更高iops的硬體,交給硬體來解決軟體的問題(沒辦法的辦法)