從一個群友問題看流複製實現原理

張哥說技術發表於2023-12-12

來源:PostgreSQL學徒

前言

這個問題是前幾天②群一位群友問的

問個問題,未提交得事務,WAL也會同步到從節點嗎?

當時我的回覆是:"嗯,然後根據commit和rollback,我下來確認一下"。藉著這個問題,我又重新思考了一下流複製的原理🤔,又有了一些新的思考,甚好甚好。

分析

首先這個問題的答案無疑,是肯定會同步到從節點,這一點和邏輯複製有所不同,之前也寫過一篇邏輯複製處理大事務的演進,在14版本邏輯複製才正式引入了"流式"傳輸,即未提交的事務也會流式傳輸到訂閱端,以前都是直到事務提交時才會一股腦傳送至訂閱端,所以碰到大事務就歇菜。

從一個群友問題看流複製實現原理

但是,流複製和邏輯複製有所不同,這一點其實很好驗證 (以下截圖左邊是主庫,右邊是備庫):

從一個群友問題看流複製實現原理

主庫在事務內做建表、插入等操作,但是事務還未提交。👆🏻可以看到,備庫中pg_waldump可以解析到mytest表的oid,以及插入操作。最後,隨著主庫的提交或者回滾以決定元組的可見性,主庫提交備庫也提交,主庫回滾備庫也相應回滾。

從一個群友問題看流複製實現原理

現在我們新插入一條資料,各位可以看到,備庫 startup 也"及時"回放到頁面上了。但是主庫上由於事務還未提交,因此備庫不可見,但是底層資料塊上已經有這條資料了,說明startup程式已經及時回放了這條WAL。

從一個群友問題看流複製實現原理

因此,備庫也會產生死元組 (update、delete、事務回滾等),也需要相應進行 VACUUM,移除死元組,從而導致流複製場景中一個很常見的衝突——snapshot conflict,即快照衝突,備庫查詢需要的行版本被 VACUUM 移除了,從而報錯:User query might have needed to see row versions that must be removed.

從一個群友問題看流複製實現原理

所以,一環扣一環,從很多平時細節,我們就可以倒推出這個原理。我們不如通俗地理解成:備庫按部就班,原模原樣復刻主庫的操作。主庫回滾我就回滾,主庫刪除我就刪除,也正是這種資料塊級別的物理複製機制,使得流複製有著其獨特的優勢——一致性。

深入剖析

作為一名鹹魚DBA,需要有打破砂鍋問到底的精神,我們不妨思考一下,PostgreSQL的流複製原理,底層是如何實現的?此處站在巨人的肩膀上——SUZUKI,內幕指南已經更新了流複製章節,我也很早就看了,,其中也有介紹流複製的傳輸細節:

從一個群友問題看流複製實現原理
  • (1) The backend process writes and flushes WAL data to a WAL segment file by executing the functions XLogInsert() and XLogFlush().
  • (2) The walsender process sends the WAL data written into the WAL segment to the walreceiver process.
  • (3) After sending the WAL data, the backend process continues to wait for an ACK response from the standby server. More precisely, the backend process gets a latch by executing the internal function SyncRepWaitForLSN(), and waits for it to be released.
  • (4) The walreceiver on the standby server writes the received WAL data into the standby's WAL segment using the write() system call, and returns an ACK response to the walsender.
  • (5) The walreceiver flushes the WAL data to the WAL segment using the system call such as fsync(), returns another ACK response to the walsender, and informs the startup process about WAL data updated.
  • (6) The startup process replays the WAL data, which has been written to the WAL segment.
  • (7) The walsender releases the latch of the backend process on receiving the ACK response from the walreceiver, and then, the backend process's commit or abort action will be completed. The timing for latch-release depends on the parameter synchronous_commit. It is 'on' (default), the latch is released when the ACK of step (5) received, whereas it is 'remote_write', the latch is released when the ACK of step (4) is received.

翻譯一下

  1. 後端程式透過執行XLogInsert和XLogFlush函式,將WAL資料寫入並重新整理到WAL段檔案中。
  2. walsender程式將寫入WAL段的WAL資料傳送給walreceiver程式。
  3. 傳送WAL資料後,後端程式繼續等待備用伺服器的ACK響應。更準確地說,後端程式透過執行內部函式SyncRepWaitForLSN獲得一個latch,並等待它被釋放。
  4. 備用伺服器上的walreceiver將接收到的WAL資料寫入備用的WAL段中,使用write系統呼叫,並向walsender返回一個ACK響應。
  5. walreceiver使用如fsync等系統呼叫將WAL資料重新整理到WAL段,向walsender返回另一個ACK響應,並通知啟動程式關於WAL資料的更新。
  6. startup程式回放已寫入WAL段的WAL資料。
  7. 當walsender收到來自walreceiver的ACK響應時,釋放後端程式的 latch,然後後端程式的提交或中止操作將完成。latch釋放的時機取決於引數synchronous_commit。如果設為on (預設值),當收到步驟(5)的ACK時釋放latch;如果設為remote_write,則在收到步驟(4)的ACK時釋放latch。

每一個ACK都包括

  • The LSN location where the latest WAL data has been written.
  • The LSN location where the latest WAL data has been flushed.
  • The LSN location where the latest WAL data has been replayed in the startup process.
  • The timestamp when this response has be sent.

根據上述傳輸細節,我們便可以對這個案例進行理解剖析了:

  1. 主庫上的程式寫操作
  2. 產生WAL record
  3. walsender感知到新的WAL,傳送給備庫,
  4. 備庫接受,寫盤,再回放

因此準確來說,是否傳送WAL與主庫的事務是否提交與否沒有關係,但主庫的事務能否提交卻取決於備庫的WAL寫到了哪裡(預設是on的話,就需要落盤),即同步級別,備庫會實時回放。

從一個群友問題看流複製實現原理

至於latch,此次PostgreSQL生態大會上,文一也做了介紹,流複製就是藉助latch實現主從程式間的協作。

從一個群友問題看流複製實現原理

小結

不難理解,流複製以低延時(相對)著稱,是否傳送WAL與主庫的事務是否提交與否沒有關係,但主庫的事務能否提交取決於備庫的WAL寫到了哪裡(synchronous_commit),假如還要等待事務提交的時候,才將所有的WAL傳送,那麼無疑延遲會很大,這也體現不了物理複製的優勢。

不過值得注意的是,流複製場景下,只讀事務、子事務的提交以及事務回滾,無需等待備庫的ACK。

Read-only transactions and transaction rollbacks need not wait for replies from standby servers. Subtransaction commits do not wait for responses from standby servers, only top-level commits. Long running actions such as data loading or index building do not wait until the very final commit message. All two-phase commit actions require commit waits, including both prepare and commit.

最後,再次推薦各位閱讀一下SUZUKI大師的最新流複製章節,相信會有更多理解。

參考

文一 PostgreSQL Latch 簡介:一種事件實現機制

Streaming Replication


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

相關文章