PostgreSQL-14版本snapshot的幾點最佳化

T1YSL發表於2022-11-17

最近在分析PostgreSQL-14版本效能提升的時候,關注到了Snapshots的這一部分。發現在PostgreSQL-14版本,連續合入了好幾個和Snapshots相關的patch。

image.png

並且,Andres Freund也透過這些改進顯著減少了已確定的快照可擴充套件性瓶頸,從而改進了Postgres處理大量連線的問題,因為在連線數很高時計算一個快照的代價是很昂貴的。(對於分析連線可擴充套件性的侷限性可以參考Andres Freund的這篇文章  )

image.png

本篇文章,我會結合Andres Freund的幾個patch進行一些分析,看看在PostgreSQL-14版本里對於Snapshots做的這部分最佳化。

在PostgreSQL裡是使用SnapshotData結構體的xip來記錄哪些事務正在執行中,正常情況下,對於普通的MVCC快照,xip記錄了所有執行中的事務ID。(除了快照是處於recovery狀態產生的,這個時候它為空)

typedef struct SnapshotData
{
	TransactionId xmin;			/* all XID < xmin are visible to me */
	TransactionId xmax;			/* all XID >= xmax are invisible to me */
	TransactionId *xip;
	uint32		xcnt;			/* # of xact ids in xip[] */
	TransactionId *subxip;
        int32	    subxcnt;		/* # of xact ids in subxip[] */
...

在PostgreSQL-14版本之前,每個PG程式都對應一個PGPROC結構、一個PGXACT結構,而從PostgreSQL-14版本開始,把PGXACT合到PGPROC裡面了。

一、最佳化一:在構建快照時不計算全域性 horizons

這個patch的地址為

為了使GetSnapshotData()具有更強的可伸縮性,它不能不檢視每個程式的xmin:儘管快照內容不需要在提交只讀事務或釋放快照時更改,但在這些情況下會修改程式的xmin。xmin修改的頻率很快,它會導致GetSnapshotData()中的許多快取丟失,儘管快照底層的資料沒有改變,尤其是在核心計數較高的系統中。這是GetSnapshotData()在大型系統上擴充套件性差的最重要原因。

這個提交其實只是刪除對PGXACT->xmin的訪問,讓GetSnapshotData()不再需要訪問xmins,這本身不會帶來顯著的改善,但是為後續的幾個最佳化都是針對GetSnapshotData()不需要訪問xmins去做的。

二、最佳化二:將PGXACT->xmin移回PGPROC

這個patch的地址為 

早在2011年其實就已經發現了GetSnapshotData存在瓶頸,當時做的最佳化是把PGPROC裡面把快照需要的變數拆出來,放到PGXACT中,這樣資料結構小很多,可以裝到一個cpu cacheline中。

這個patch它把第一部分GetSnapshotData()不再需要的xmin移回了PGPROC。原本在PGXACT裡使用xmin的時候,它比PGXACT裡的其他成員更新更頻繁,進而導致cacheline有很多不必要的ping-pong,所以又將PGXACT->xmin移回了PGPROC結構體。

這幾句話大一看好像有點一頭霧水,什麼是cacheline,什麼是ping-pong。
這裡給大家大致解釋這幾個名詞,方便我們更好去理解,文章底部也有我參考的連結。

1、cacheline

記憶體是DRAM,速度慢,容量大。屬於CPU外部裝置,透過匯流排與CPU進行通訊。而cache 在 CPU內部,屬於片上硬體。所以記憶體的速度會遠遠慢於cache。

cache的單位是cacheline,是一塊連續的記憶體。以cacheline 64位元組為例。所有的cacheline 又劃到各個way下。row: 叫做set。columne: 叫做way
image.png
當CPU訪問一個記憶體的時候,透過記憶體中間的6bits定位在哪個set,再透過24bits定位響應的cacheline。
對同一塊cache不停的進行寫操作,會增加cache之間更新快取的花銷。包括不停的去重新整理主存的內容。快取的cacheline的state machine 頻繁的從 S -> I 狀態

2、ping-pong

Ping-pong是一種資料緩衝的手段(是一種資料傳輸技術),能夠同時利用兩個相同資料緩衝區達到資料連續傳輸的目的(物件型別可以是任意的),兩者交替地被讀和被寫,從而提高資料傳輸速率。
image.png

然後再去看開頭的那句話,是不是大致理解了,這個patch主要減少了在這個函式里使用xmin所導致的cache之間頻繁更新快取的花銷,對於高度併發、快照獲取量大、工作負載大的情況,僅此更改就可以顯著提高可伸縮性。

三、最佳化三:將PGXACT->vacuumFlags移動到ProcGlobal->vacuumFlags

這個patch的連結為

這個patch把PGXACT->vacuumFlags 移至ProcGlobal,增加了GetSnapshotData()經常需要的資料儲存在L2快取中的機會,L2快取位於CPU與記憶體之間的臨時儲存器,容量比記憶體小但交換速度快,二級快取容量大小決定了cpu的效能。

L1 L2 L3快取和記憶體的快慢關係,可以參考如下:

L1的存取速度:4個cpu時鐘週期
L2的存取速度:11個cpu時鐘週期
L3的存取速度:39個cpu時鐘週期
RAM的存取速度:107個cpu時鐘週期

可以看到,L2快取的速度比在記憶體裡快,因此這樣的改動,相當於加快了GetSnapshotData()執行的速度。

四、最佳化四:移動subxact資訊到ProcGlobal,刪除PGXACT

這個patch的連結為

與第三個最佳化的更改類似,這個patch增加了GetSnapshotData()經常需要的資料儲存在L2快取中的機會。在許多workload中,子事務是非常罕見的,這使得檢查的代價很小。
把PGXACT的所有成員都轉移了,所以不需要再保留它了,因此刪除了PGXACT結構體。

五、最佳化五: 引入正在進行的xid的密集陣列

這個patch的連結為

之前構建快照時,GetSnapshotData會遍歷procArray->pgprocnos找到所有存在的PGPROC、PGXACT結構,收集所有的xid,即透過pgprocnos拿到索引,透過索引在離散的allPgXact陣列中拿到xid。而現在的版本,其實改成了將xids單獨拿出來放到連續儲存的密集陣列中,來顯著提高其命中率。

六、最佳化六:使用xact完成計數器快取快照

這個patch的連結為

這個提交在VariableCacheData的結構體裡引入了xactCompletionCount(VariableCacheData是共享記憶體中的一個資料結構,用於跟蹤OID和XID賦值狀態),它跟蹤自伺服器啟動以來以某種形式完成的帶有xid(即可能修改了資料庫)的Top事務的數量。PostgreSQL-14版本僅用於檢查GetSnapshotData()是否需要重新計算快照的內容。只要當前的xactCompletionCount與最初構建快照時相同,就可以避免重新構建快照的內容。
這個改動的意圖很明顯了,前幾個最佳化都是讓計算快照變得更快/更可伸縮,而這個改動是:避免重新構建快照的內容,減少資源佔用。(也就是能不重新做快照就不做快照)。

image.png

參考列表

1.Improving Postgres Connection Scalability: Snapshots(Andres Freund)
https://www.citusdata.com/blog/2020/10/25/improving-postgres-connection-scalability-snapshots/#unfortunately-a-minimal-transaction-visibility-primer
2.cacheline

3.ping-pong機制
https://blog.csdn.net/chenyu201003/article/details/81449762
4.德哥的PostgreSQL 20200819當天程式碼 - 14 對比 13 高併發效能最佳化 資料對比 - get snapshot improve
https://github.com/digoal/blog/blob/master/202008/20200817_01.md


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69990629/viewspace-2923809/,如需轉載,請註明出處,否則將追究法律責任。

相關文章