PostgreSQL , 分區表 , bind , spin lock , 性能分析 , sleep 程序 , CPU空轉 , cache
實際上我寫過很多文檔,關于分區表的優化:
<a href="https://github.com/digoal/blog/blob/master/201801/20180122_03.md">《PostgreSQL 商用版本EPAS(阿裡雲ppas) - 分區表性能優化 (堪比pg_pathman)》</a>
<a href="https://github.com/digoal/blog/blob/master/201711/20171122_02.md">《PostgreSQL 傳統 hash 分區方法和性能》</a>
<a href="https://github.com/digoal/blog/blob/master/201710/20171015_01.md">《PostgreSQL 10 内置分區 vs pg_pathman perf profiling》</a>
實際上native分區表的性能問題主要還是在于分區表過多的時候,執行計劃需要耗時很久。
是以有了
1、PPAS的edb_enable_pruning參數,可以在生成執行計劃前,使用簡單SQL的話,直接過濾到目标分區,進而不需要的分區不需要進入執行計劃的環節。
2、pg_pathman則支援的更全面,除了簡單SQL,複雜的表達式,immutable都可以進行過濾,過濾到目标分區,進而不需要的分區不需要進入執行計劃的環節。
因分區表過多引發的問題通常出現在OLTP系統(主要是OLTP系統的并發高,更容易把這種小問題放大),本來一次請求隻需要1毫秒的,但是執行計劃可能需要上百毫秒,也就是說執行耗時變成了小頭,而執行計劃(SPIN LOCK)變成了大頭。
下面這個例子也是OLTP系統相關的,有具體的原因分析。
某個業務系統,單次SQL請求很快,幾十毫秒,但是并發一高,QPS并沒有線性的增長。
而且大量的程序處于BIND,SLEEP的狀态。

經過診斷,
<a href="https://github.com/digoal/blog/blob/master/201611/20161129_01.md">《PostgreSQL 源碼性能診斷(perf profiling)指南》</a>
<a href="https://github.com/digoal/blog/blob/master/201611/20161127_01.md">《Linux 性能診斷 perf使用指南》</a>
主要的原因是大量的SPIN LOCK,導緻CPU空轉。
比如某個程序BIND時的pstack
由于業務使用了prepared statement,是以過程會變成bind 過程
1、prepare statement
2、bind parameters
3、代入參數、(設定了constraint_exclusion時)判斷哪些分區需要被過濾
4、execute prepared statement
在find_all_inheritors過程中,涉及的分區表過多,最後每個分區都要取LOCK(後面加載了系統的spin lock),是以我們會看到CPU很高,同時大量的BIND,程序處于SLEEP狀态,也就是CPU空轉,CPU時間片被獨占的狀态。
自旋鎖是專為防止多處理器并發而引入的一種鎖,它在核心中大量應用于中斷處理等部分(對于單處理器來說,防止中斷進行中的并發可簡單采用關閉中斷的方式,不需要自旋鎖)。
自旋鎖最多隻能被一個核心任務持有,如果一個核心任務試圖請求一個已被争用(已經被持有)的自旋鎖,那麼這個任務就會一直進行忙循環——旋轉——等待鎖重新可用。
要是鎖未被争用,請求它的核心任務便能立刻得到它并且繼續進行。自旋鎖可以在任何時刻防止多于一個的核心任務同時進入臨界區,是以這種鎖可有效地避免多處理器上并發運作的核心任務競争共享資源。
事實上,自旋鎖的初衷就是:
在短期間内進行輕量級的鎖定。一個程序去擷取被争用的自旋鎖時,請求它的線程在等待鎖重新可用的期間進行自旋(特别浪費處理器時間),是以自旋鎖不應該被持有時間過長(等待時CPU被獨占)。如果需要長時間鎖定的話, 最好使用信号量(睡眠,CPU資源可出讓)
。
簡單的說,自旋鎖在核心中主要用來防止多處理器中并發通路臨界區,防止核心搶占造成的競争。另外自旋鎖不允許任務睡眠(持有自旋鎖的任務睡眠會造成自死鎖——因為睡眠有可能造成持有鎖的核心任務被重新排程,而再次申請自己已持有的鎖),它能夠在中斷上下文使用。
死鎖:假設有一個或多個核心任務和一個或多個資源,每個核心都在等待其中的一個資源,但所有的資源都已經被占用了。這便會發生所有核心任務都在互相等待,但它們永遠不會釋放已經占有的資源,于是任何核心任務都無法獲得所需要的資源,無法繼續運作,這便
意味着死鎖發生了。自死瑣是說自己占有了某個資源,然後自己又申請自己已占有的資源,顯然不可能再獲得該資源,是以就自縛手腳了。
spinlock特性:
防止多處理器并發通路臨界區,
1、非睡眠(該程序/LWP(Light Weight Process)始終處于Running的狀态)
2、忙等 (cpu一直檢測鎖是否已經被其他cpu釋放)
3、短期(低開銷)加鎖
4、适合中斷上下文鎖定
5、多cpu的機器才有意義(需要等待其他cpu釋放鎖)
以下截取自
<a href="http://blog.sina.com.cn/s/blog_458d6ed5010110hv.html">http://blog.sina.com.cn/s/blog_458d6ed5010110hv.html</a>
Spinlock的目的是用來同步SMP中會被多個CPU同時存取的變量。在Linux中,普通的spinlock由于不帶額外的語義,是用起來反而要非 常小心。 在Linux kernel中執行的代碼大體分normal和interrupt context兩種。tasklet/softirq可以歸為normal因為他們可以進入等待
Spinlock的目的是用來同步SMP中會被多個CPU同時存取的變量。在Linux中,普通的spinlock由于不帶額外的語義,是用起來反而要非常小心。
在Linux kernel中執行的代碼大體分normal和interrupt context兩種。tasklet/softirq可以歸為normal因為他們可以進入等待;nested interrupt是interrupt context的一種特殊情況,當然也是interrupt context。Normal級别可以被interrupt搶斷,interrupt會被另一個interrupt搶斷,但不會被normal中斷。各個 interrupt之間沒有優先級關系,隻要有可能,每個interrupt都會被其他interrupt中斷。
我們先考慮單CPU的情況。在這樣情況下,不管在什麼執行級别,我們隻要簡單地把CPU的中斷關掉就可以達到獨占處理的目的。從這個角度來說,spinlock的實作簡單地令人乍舌:cli/sti。隻要這樣,我們就關閉了preemption帶來的複雜之門。
單CPU的情況很簡單,多CPU就不那麼簡單了。單純地關掉目前CPU的中斷并不會給我們帶來好運。當我們的代碼存取一個shared variable時,另一顆CPU随時會把資料改得面目全非。我們需要有手段通知它(或它們,你知道我的意思)——spinlock正為此設。這個例子是 我們的第一次嘗試:
他能正常工作嗎?答案是有可能。在某些情況下,這段代碼可以正常工作,但想一想會不會發生這樣的事:
喔,我們在normal級别下獲得了一個spinlock,正當我們想做什麼的時候,我們被interrupt打斷了,CPU轉而執行interrupt level的代碼,它也想獲得這個lock,于是“死鎖”發生了!解決方法很簡單,看看我們第二次嘗試:
在獲得spinlock之前,我們先把目前CPU的中斷禁止掉,然後獲得一個lock;在釋放lock之後再把中斷打開。這樣,我們就防止了死鎖。事實上,Linux提供了一個更為快捷的方式來實作這個功能:
如果沒有nested interrupt,所有這一切都很好。加上nested interrupt,我們再來看看這個例子:
Code 1和code 2都運作在interrupt context下,由于中斷可以嵌套執行,我們很容易就可以想到這樣的運作次序:
問題是在第一個spin_unlock_irq後這個CPU的中斷已經被打開,“死鎖”的問題又會回到我們身邊!
解決方法是我們在每次關閉中斷前紀錄目前中斷的狀态,然後恢複它而不是直接把中斷打開。
Linux同樣提供了更為簡便的方式:
使用這篇文檔提到的方法,建立幾千個分區的分區表,然後使用pgbench壓測就可以複現這個問題。
不再贅述。
優化方法:
1、假設我們的QUERY程序要查詢多個分區(指很多個分區),那麼建議把分區的粒度降低,盡量讓QUERY減少真正被通路的分區數,進而減少LWLockAcquire次數。
2、如果我們的分區很多,但是通過QUERY的WHERE條件過濾後實際被通路的分區不多,那麼分區表的選擇就非常重要。(目前盡量不要使用NATIVE分區)。盡量使用PPAS的edb_enable_pruning。對于PostgreSQL社群版本使用者,在社群優化這部分代碼前,請盡量使用pg_pathman分區功能。
<a href="https://github.com/digoal/blog/blob/master/201611/20161109_02.md">《Linux中的spinlock和mutex》</a>
<a href="https://github.com/digoal/blog/blob/master/201801/20180113_04.md">《PostgreSQL 商用版本EPAS(阿裡雲ppas) NUMA 架構spin鎖等待優化》</a>