oom是實例使用內存超過實例規格內存上限導致進程被kill,實例存在秒級的不可用。mysql的內存管理比較複雜,內存監控需要開啟performance schema查詢(默認關閉),會帶來額外的內存消耗和性能損失,在不開啟performance schema情況下排查內存使用情況又比較困難。本文將基於tdsql-c(基於mysql5.7)總結一下在線上經常出現的一些oom的場景、排查手段及相應的優化方案。
一、mysql線上常見oom問題
1.1 表數量較多導致innodb數據字典內存佔用多
查詢命令:show engine innodb status; 如下,dictionary memory allocated 顯示數據字典內存已經佔用約8g了,這部分內存不包含在 buffer pool 總內存大小中。
數據字典內存佔用和innodb表的數量,表定義,table_open_cache,並發連接數等因素有關。
可以看到數據字典表有20w+,索引有70w+,對於這種場景要解決oom風險,在不損失性能的前提下可以考慮升級內存規格。若能接受性能損失,可以降低innodb_buffer_pool_size或者table_open_cache來緩解內存開銷。
1.2 大query帶來內存上漲
若觀察到實例內存抖動與業務流量增長一致,基本確定實例內存增長是用戶連接內存開銷導致。
通過performance schema來查看具體是哪一塊內存佔用過多:
通過show detail processlist(tdsql-c 自研功能)對單個連接佔用內存情況進行查詢:
server_memory_used: 該連接server層內存大小
innodb_memory_used: 該連接innodb層內存大小
pfs_memory_used: 該連接performance schema內存大小
os_memory_used: 從jemalloc層面上統計該連接內存大小
query_memory_used: 從jemalloc層面上統計當前query的內存大小
單個連接佔用內存過多,可以採用開啟線程池限制並發連接數,或者升級內存規格。對於insert多value佔用過多內存可以在業務側進行sql拆分。
1.3 業務sql使用了prepare statement緩存
prepare statement cache用來緩存語句解析後的執行計劃,緩存的語句越多,每個session所佔用的內存也就越多。以sysbench為例,sysbench 1.1 默認打開了ps,導致prepare_statement緩存佔用內存過大觸發oom。
升級內存規格可以緩解oom,若能接受少量性能損失可以不使用ps緩存(例如sysbench--db-ps-mode=disable關閉ps),或者限制max_prepared_stmt_count大小。
1.4 業務連接數過多
小內存規格的實例出現過萬的連接數,連接佔用過多內存導致頻繁oom,可以通過開啟線程池進行限制。
1.5 net buffer過大導致實例頻繁oom
如下有個實例的內存增長隨負載的變化呈螺旋上升趨勢:
開啟performance schema後觀察到是net::buffer的內存在持續上漲。
通過以下sql查詢具體哪些連接佔用了net::buffer的內存:
大量連接使用了16mb大小的net buffer內存,這裡的具體原因是用戶的sql比較大(大於max_packet_length,16mb),對於長連接來說執行完query這16mb緩存不會立即釋放,用作下一次query的connection buffer,用戶使用了大量的長連接導致這部分內存增長很快。
升級實例內存規格、業務側減小每個sql的大小或者降低連接數可以解決。
1.6 內核bug導致內存泄露引起oom
使用valgrind查看是否有內存泄漏:
1. 下載valgrind
2. 安裝valgrind:1 ./configure 2 make 3 make install 4 valgrind -h
3. 使用valgrind拉起mysqld
4. 給實例加負載
5. shutdown實例,內存檢查結果輸出到valgrind_log中
6. valgrind_log最後會打印內存泄漏的總體情況,再去找各堆棧的情況
"definitely lost":確認丟失。程序中存在內存泄露,應儘快修復。當程序結束時如果一塊動態分配的內存沒有被釋放且通過程序內的指針變量均無法訪問這塊內存則會報這個錯誤。
"indirectly lost":間接丟失。當使用了含有指針成員的類或結構時可能會報這個錯誤。這類錯誤無需直接修復,他們總是與"definitely lost"一起出現,只要修復"definitely lost"即可。
"possibly lost":可能丟失。大多數情況下應視為與"definitely lost"一樣需要儘快修復,除非你的程序讓一個指針指向一塊動態分配的內存(但不是這塊內存起始地址),然後通過運算得到這塊內存起始地址,再釋放它。
二、tdsql-c簡介
隨着互聯網的發展,各種業務數據快速膨脹,用戶對數據庫計算和存儲能力的需求日益增長。在應對業務需求持續增長時,傳統數據庫的迭代和優化已經變得舉步維艱,而分佈式架構的優勢則愈發明顯。藉助計算存儲分離的架構,新硬件優勢,物理複製特點,分佈式系統優勢,tdsql-c對比傳統mysql具有高性能,低成本,大存儲,主從複製延遲低,秒級擴縮容,極速回檔,serverless化等優勢。
前面講了tdsql-c相對傳統數據庫的優勢,接下來介紹tdsql-c在內存使用方面相對傳統mysql在內存使用方面存在哪些弊端。
從下面的對比圖可以看出,傳統mysql的數據,邏輯日誌,物理日誌,元數據都是存在本地盤,主從管理各自的數據,通過邏輯日誌進行主從同步。
tdsql-c分為計算層和存儲層,本地不再存儲任何數據,共享存儲層數據,主從通過物理日誌進行同步,存儲層通過接受主庫發送的物理日誌進行回放生成數據及元數據,不再需要邏輯日誌。架構的巨大改變帶來了以下問題:
1. tdsql-c卸載了本地io, 不再保留redo log file,而是在內存中增加了一個可以覆蓋寫的日誌發送緩存區,相對傳統mysql會帶來額外的內存開銷。
2. tdsql-c增加了主備之間、計算節點和存儲節點之間的通信節點管理,計算節點遠程page io任務隊列維護,相關監控信息採集,備機物理日誌回放等也會帶來相應的內存開銷。
三、tdsql-c oom 優化
3.1 tdsql-c server端參數優化
我們在不影響數據庫性能的前提下修改實例默認配置來降低內存佔用(括號內為優化後的默認值),主要包括以下參數的調整:
innodb_log_buffer_size: 用來設置緩存還未提交的事務的緩衝區的大小
innodb_ncdb_log_buffer_size:該參數對主庫來說相當於innodb_log_file_size,對於備機來說相當於日誌接受緩衝buffer
key_buffer_size:key_buffer主要用於緩存myisam index block,tdsql-c不支持myisam存儲引擎
innodb_ncdb_wait_queue_size:開啟異步組提交後,innodb_ncdb_wait_queue_size表明最少可以同時容納的事務異步提交數量,超過後需要同步等待
innodb_ncdb_log_flush_events:喚醒等待log flush的event的個數
實驗驗證性能是否下降以及內存佔用是否減少:
實例規格:2c4g 一主一從
測試場景:分別用1g和100g的數據量對應cpu bound和io bound場景進行sysbench讀寫性能測試
測試結論:在性能無顯著變化的情況下,2c4g規格的實例實際內存佔用減少了約200mb。
壓測後觀察實例的實際內存佔用情況:
3.2 支持information_schema.detail_processlist快捷查詢各連接數內存使用
進一步支持將show detail processlist的結果存儲到information_schema.detail_processlist,便於以下查詢:
按內存使用量排序查詢出使用量top n的鏈接;
計算所有連接內存使用量的總大小;
其他查詢類似聚合或者top類的字段;
3.3 支持innodb buffer pool冷熱page數量查詢,為用戶推薦合理的innodb_buffer_pool
統計在一段時間內沒被訪問的page的數量,反映出來用戶真正需要多大的buffer pool,便於自動縮容到用戶需要用的 bp 上。
內核新增參數:innodb_hot_page_time,單位秒,表示一定時間內訪問過的page都是熱page。
新增命令:show coldpage status,表明在buffer pool中,在innodb_hot_page_time時間內沒有被訪問過的page數量。
用戶可以根據業務情況設置innodb_hot_page_time計算出準確的熱數據量,根據熱數據設置合理的buffer pool size。
3.4 限制innodb_buffer_pool的最大使用率,降低oom的風險
實例啟動後,innodb buffer pool隨着使用率的增長,內存分配也逐漸增加,假如innodb buffer pool使用率未達到100%,但是實例存在oom的風險,通過設置
innodb_max_lru_pages_pct限制innodb buffer pool的實際使用率,避免innodb buffer pool內存進一步增加導致oom。
3.5 resize innodb buffer pool 性能優化,減小動態設置innodb buffer pool對業務的影響
對於有oom風險的實例可以通過動態調整innodb buffer pool大小進行規避。但是對大實例進行調整innodb buffer pool往往會造成性能抖動。
如下圖所示分別是動態增大和減小innodb buffer pool的過程。增大buffer pool size的過程比較簡單,對並發負載沒有太大影響。減小buffer pool size的過程需要將回收區的page轉移到非回收區,這個過程需要長時間持有buffer pool mutex,阻塞其他線程無法訪問buffer pool。
tdsql-c對resize buffer pool回收page過程進行了性能優化,優化後僅需對回收區的page持有buffer pool mutex。
以下是bp在33g和22g之間每隔60s resize 一次,同時利用sysbench進行讀寫壓測,持續觀察qps變化情況。
根據結果可以看到優化後的性能抖動減小,性能下降維持時間縮短。大大減小了動態設置innodb buffer pool對業務的影響。
四、總結
內存溢出一直是軟件開發的「老大難」問題,更何況數據庫環境更加複雜,sql語法、數據類型、數據大小、並發數、mysql參數配置等這些因素都與內存有關。tdsql-c內核團隊在tdsql-c的內存管理上進行一系列的優化,包括優化server端參數配置降低內存佔用、豐富內存監控、增加buffer pool冷熱page數查詢方便用戶設置更合理的buffer pool大小、在即將面臨oom風險時限制innodb_buffer_pool的最大使用率避免內存用盡觸發oom、優化動態調整buffer pool大小對並發業務的影響。後續我們也會持續進行優化,不斷提升tdsql-c的穩定性和可用性,為用戶帶來更好的產品體驗。