postgresql , linux , oom , sql審計 , palloc , rss
資料庫是比較重記憶體的應用軟體之一,比如排序、聚合、使用較大的列、使用很長的sql(值sql本身的内容長度),或者傳入很大的變長類型值時,都可能使得單個連接配接就會消耗很大的記憶體。
而另一方面,每個連接配接會消耗一定的記憶體,比如syscache , relcache,随着通路的對象變多,如果是長連接配接,消耗的記憶體也會越多。 通常來說,長連接配接當通路了很多中繼資料時,可能占用幾十mb到上百mb不等。
當使用了cgroup來限制資料庫執行個體的總記憶體時,随着資料庫占用的rss部分的記憶體越來越多,如果資料庫連接配接在申請記憶體時,超出了cgroup的限制,則連接配接可能被oom掉。
當然,即使不使用cgroup,linux也會根據核心的配置,以及使用者申請記憶體的動作,當時的剩餘記憶體等情況綜合,發生oom。
有幾篇文章可以參考一下
<a href="https://github.com/digoal/blog/blob/master/201606/20160608_01.md">《精确度量linux下程序占用多少記憶體的方法》</a>
<a href="https://github.com/digoal/blog/blob/master/201608/20160824_01.md">《一個笛卡爾積的update from語句引發的(記憶體洩露?)問題》</a>
<a href="https://github.com/digoal/blog/blob/master/201607/20160709_01.md">《postgresql relcache在長連接配接應用中的記憶體霸占"坑"》</a>
<a href="https://github.com/digoal/blog/blob/master/201612/20161221_01.md">《linux page allocation failure 的問題處理 - lowmem_reserve_ratio》</a>
當發生了oom後,如何找到引起oom的那個會話,他是罪魁禍首(指單個會話申請了過多的記憶體),還是壓死駱駝的最後一根稻草(指連接配接數過多)呢?
資料庫又是如何申請記憶體的呢?
簡單講一下rss和cache.
rss是程式申請的記憶體,不能被核心自動釋放,由使用者自己來管理它什麼時候被釋放。
cache,通常指緩存,比如檔案系統的緩存,不由使用者程序來管理,它可以被核心釋放。
我們所指的記憶體不足,是指即使cache完全被釋放,也無法配置設定足夠的記憶體給使用者請求。
在cgroup的memory子系統中,我們可以看到這兩個部分,rss很多,cache很少時,就要注意了,可能會發生oom。
以postgresql資料庫為例,為了管理友善,pg使用統一的記憶體配置設定和釋放api,便于管理,詳見src/backend/utils/mmgr/mcxt.c。
用得比較多的比如palloc,它申請的記憶體某些時候可以自動被回收,比如事務結束,會話斷開,query結束時,使用palloc申請的某些記憶體,會自動被釋放。
還有一些記憶體配置設定接口詳見src/backend/utils/mmgr/mcxt.c。
處理單個值時,記憶體限制。單條sql的記憶體限制也是1gb。
使用者可以根據mem context存儲記憶體配置設定和釋放的方法,應對不同的使用場景,這樣保證了接口依舊是palloc。
我們可以在資料庫的類型進行中,大量的用到了palloc,同時也可以很清晰的了解,每一種類型,在使用是需要消耗多少記憶體。
cd postgresql-9.6.1/src/backend/utils
比如字元串,數組,全文檢索,這些屬于變長類型,最長允許1gb,在申請記憶體時,根據被處理的值的實際的大小申請。
為了友善在一台實體機中啟動多個執行個體,隔離資源,使用cgroup是一個很不錯的方法。
把執行個體程序号寫入tasks即可。
為了測試友善,我這裡限制了100mb, 并且隻将測試會話的backend process加入tasks
檢視目前狀态
因為限制了100mb,是以這個資料庫backend process需要申請超過100mb的記憶體,才會被oom。
根據前面講的,排序、聚合、使用較大的列、使用很長的sql(值sql本身的内容長度),或者傳入很大的變長類型值時,都可能使得單個連接配接就會消耗很大的記憶體。
注意每次被oom後,重連,并将新的backend pid寫入cgroup的tasks再測試下一輪.
1. 以使用較大的列為例,将1億個值,聚合為一個數組,由于數組屬于變長類型,最長可以放1gb,1億已經超過1gb了,是以觸發了oom。
2. 對較大的表排序,并且設定較大的work_mem
使用較小的work_mem不會被oom,因為使用了臨時檔案。
3. autovacuum worker程序啟動後,單個worker程序可能需要申請的記憶體大小為maintenance_work_mem或者vacuum_work_mem。
4. 并行query
5. 帶有多個hash join,多個排序操作的複雜query,可能消耗多份work_mem。
這種操作不需要很多記憶體:
比如查詢了一張很大的表,傳回了大批量(比如一億)記錄,即使不使用流式接收,也不需要很多記憶體。
終于說到審計了,沒錯,當oom發生後,我們怎麼找到壓死駱駝的最後一根稻草,或者是罪魁禍首呢?
由于oom發的是kill -9的信号,被kill的程序根本無法通過捕獲信号來記錄當時正在執行的query或者當時的狀态。
那麼審計就很有用了。
有這麼幾個參數
顯然,如果我們需要在oom後,還能找到被oom程序當時執行query的蛛絲馬迹,方法1:在請求時就記錄下它在執行什麼(開啟log_statement='all'),方法2:記錄detail字段,postmaster程序會收集這部分資訊,不管什麼方法,超過track_activity_query_size長度的query都被截斷。
例如開啟log_statement='all';後,我們能在日志中看到這樣的資訊。