HybridDB · 穩定性 · HybridDB如何優雅的處理Out Of Memery問題
前言
你是否遇到過數據庫服務器的Out Of Memory(OOM)現象?就是數據庫的進程把操作係統內存耗盡,觸發操作係統對數據庫進程執行Kill -9操作。操作係統對某個數據庫進程的Kill,會導致整個數據庫實例所有實例重啟,所有連接會斷開,造成一定時間的數據庫不可用。OOM對數據庫服務影響較大,應該盡量避免。
在我們的HybridDB for PG 雲服務中,也可能遇到用戶實例耗盡所有可用內存的情況。一般情況下,我們會采用CGROUP機製來限製用戶的內存使用,同一實例在同一主機上的所有進程會放入一個CGROUP。當數據庫實例使用的內存總量超出限製時,會觸發操作係統從CGROUP中找出耗內存較多的進程,執行Kill -9,這會導致我們上麵說的數據庫服務暫時不可用。這裏操作係統的Kill操作我們稱為OOM Kill。
發生OOM的因素一般是應用的並發連接數過多、大對象的存取操作、查詢用到過多的臨時內存,實例內存過小等。從雲服務提供者的角度看,我們無法提前預知或限製用戶的使用行為。通常是發生OOM Kill後,監控程序檢測到操作係統錯誤日誌,我們才會進行處理,而此時往往已經對用戶業務造成了影響。就是說,我們無法避免OOM的發生。但能不能更好的處理OOM,避免OOM Kill這種嚴重後果呢?本文將對此進行討論。
Greenplum的處理方式
HybridDB for PG 是基於Greenplum開發的。為盡量避免OOM Kill,Greenplum提供了Resource Manager內存限製功能。通過設置Resource Manager參數,可以限製整個實例可以申請的內存。其背後的機製是,在每次數據庫內核執行Malloc係統調用,申請操作係統內存時,用一個全局變量累加記錄申請的內存量,並它記錄的實例內存申請總量是否已經超限,如果超限則報錯。
Greenplum這種方法看似可行,但在我們的雲服務中卻不宜采用。這是因為,操作係統並不是在Malloc被調用的時候,把實際的物理內存返回給調用者,而是等到調用者實際使用(例如做內存拷貝操作)時,才分配物理內存給它。也就是說。記錄的Malloc內存總量,是虛擬內存使用量(可以從top命令輸出中VIRT字段,查看一個進程的虛擬內存),並不能反映數據庫實際使用的內存(實際使用內存量可以從top命令輸出中的RSS字段得到)。如果按這種方法做限製,用戶實際可用內存會比他購買的規格內存內存少的多。在我們的一個測試中發現,Malloc記錄的內存有時是實例實際使用內存的兩倍以上!
既然使用Resource Manager記錄和限製內存使用的方法不可行,有沒有更優雅的方式盡量避免OOM Kill呢?使用HybridDB的用戶,如果線下使用過Greenplum,可能會發現,在大量使用內存時,線下會觸發OOM Kill的場景在HybridDB可能並未觸發(雖然還是會有SQL報錯,但並沒有觸發實例重啟)。另一方麵,用戶可以實際使用到HybridDB規格標稱的內存。實際上HybridDB使用了獨特的的方式處理OOM,較大程度上避免了OOM Kill被觸發。下麵我們介紹一下HybridDB的方法。
HybridDB如何處理OOM
下圖是個HybridDB實例的例子。主節點、兩個子節點分布位於不同的Linux主機上。每個節點都使用CGROUP限製了內存,例如,每個節點限製使用8G內存。
我們假設上述實例的某個節點使用了超過8G的內存,如果按原有機製,此節點所在主機的操作係統,會找出此實例對應CGROUP中使用內存較多的進程,執行Kill -9,此節點的所有進程重啟,暫時不可用。更嚴重的是,這可能使整個HybridDB集群不可用!為避免這種情況,HybridDB使用了如下方法:
a. 創建一個獨立的CGROUP,限製例如10G的內存,如下圖所示。
b. 將實例的CGROUP內存限製提升20%,即如果用戶實例的節點規格是8G的內存,則在CGROUP中,提升為9.6G。這樣做的目的是為下麵的操作留存空間。
c. 啟動一個腳本,實時監控(例如每秒鍾掃描一次)主機所有CGROUP的內存水位。操作係統提供了機製,在CGOUP狀態信息中可以查到內存的水位信息。當發現某個CGROUP的水位過高(例如超過了100%的規格可用內存,如8G)時,將內存使用最高的進程移入公用CGROUP。如果內存水位未降低到指定水位,如規格內存的80%,則繼續在此CGROUP中取出內存占用高的進程,放入公用CGROUP。如下圖所示。
d. 啟動另一個獨立的腳本,不斷獲取公用CGROUP中的進程,對這些進程發送特殊的信號;HybridDB進程收到這些信號,將執行類似Cancel Query一樣的操作,撤銷當前正在進行的查詢,同時返回給用戶如下的提示:
ERROR: out of memory, no enough instance memory available to run this query.
這個步驟如下圖所示。
e. b中提到的內存監控腳本在內存降低到指定水位(如規格內存的80%)後,則進入到另一狀態,即開始將進程從公用CGROUP移動回原CGROUP,直到水位上升到預設水位(如100%規格內存)。如下圖所示。
上述方法的優勢有:
1)由於在實例內存不斷增長的過程中,實時的監控內存使用,在內存超限時,觸發查詢的撤銷操作,保護了實例,避免的實例的整體不可用。
2)用戶可以通過出錯信息獲知內存不足導致查詢失敗,進而做進一步處理。如果觸發了OOM Kill,用戶隻能看到連接斷開、實例重啟,但無法獲知發生現象的原因,所以通常會誤以為是網絡連接問題。
3)由於一台機器的所有實例都共享一個公用CGROUP,而這個CGROUP一般10G左右就滿足使用了,所以它並不會增加多少成本。另外雖然為用戶提升了20%的內存,但由於超出規格內存時就開始做查詢撤銷操作了,所以實際上實例並不會長時間多用內存。
4)這種方法實現簡單。僅需要編寫兩個監控腳本,並在HybridDB內核中增加對特殊信號的支持。
需要注意的是,如果公用CGROUP的內存也耗盡了,是會觸發OOM Kill的。也就是說這種方法並不會完全避免OOM Kill,但從實踐看,大大降低了OOM Kill的發生幾率。
總結
HybridDB對OOM的處理方式,雖然沒能避免OOM,但大大減少了OOM Kill的發生,避免了整個HybridDB集群不可用的危險後果。可以說這是一種更優雅的OOM處理方式。當然,作為HybridDB的用戶,仍然要從應用層做工作,提前降低OOM發生的幾率,例如,降低並發度、調優大量使用臨時內存的查詢、升級實例規格等。
最後更新:2017-04-21 09:30:47