Postgresql管理系列-第九章 WAL(Write Ahead Logging)介紹

事務日誌是數據庫的重要組成部分,因爲即使數據庫發生系統故障,也要求所有數據庫管理系統不能丟失任何數據。它是數據庫系統中所有更改和操作的歷史記錄日誌,以確保沒有數據因故障而丟失,例如電源故障或導致服務器崩潰的其他服務器故障。由於日誌包含有關已執行的每個事務充足的信息,因此數據庫服務器應能夠通過在服務器崩潰時重放事務日誌中的更改和操作來恢復數據庫集羣。

在計算機科學領域,WAL是Write Ahead Logging的首字母縮寫,它是將數據改動和行爲寫入事務日誌的協議或規則,而在PostgreSQL中,WAL是Write Ahead Log的首字母縮寫。該術語是事務日誌的同義詞,用於表示將事務寫入事務日誌(WAL)相關的實現機制。雖然這有點難理解,但在本文中採用了Postgresql的定義。

WAL機制最初在7.1版中實現,以減輕服務器崩潰的影響。時間點恢復(PITR)和流複製(SR)也是建議此機制之上的,這兩種情況分別在第10章和第11章中描述。

儘管對WAL機制的理解對於使用和管理PostgreSQL至關重要,但是因爲這種機制的複雜性使得無法簡單的概括和描述。所以PostgreSQL中WAL的完整解釋如下。在第一部分中,提供了WAL的整體認知相關圖片,介紹了一些重要的概念和關鍵字。在後續部分中,描述了以下話題:

  • WAL的邏輯和物理結構(事務日誌)
  • WAL數據的內部佈局
  • WAL數據寫入
  • WAL write process
  • 檢查點處理
  • 數據庫恢復處理
  • 管理WAL段文件
  • 持續存檔

9.1 概述

我們來看看WAL機制的概述。爲了澄清WAL一直在研究的問題,第一小節說明了當PostgreSQL沒有實現WAL時發生崩潰時會發生什麼。第二小節介紹了一些關鍵概念,並展示了本章主要主題的概述,WAL數據的編寫和數據庫恢復處理。最後一小節完成了WAL的概述,增加了一個關鍵概念。

在本節中,爲了簡化描述,使用了僅包含一個頁面的表TABLE_A。

9.1.1 沒有WAL的插入操作

如第8章所述,爲了提升對關係頁面的訪問效率,每個DBMS都使用了共享緩衝池。
假設我們在PostgreSQL上的TABLE_A中插入一些數據,這些數據沒有使用WAL功能;這種情況如圖9.1所示。

圖9.1 沒有WAL的插入操作
在這裏插入圖片描述
(1)發出第一個INSERT語句,PostgreSQL將TABLE_A的頁面從數據庫加載到共享緩衝池中,並將一個元組插入到頁面中。此頁面不會立即寫入數據庫。 (如第8章所述,修改後的頁面通常稱爲髒頁面。)
(2)發出第二個INSERT語句,PostgreSQL在緩衝池的頁面中插入一個新的元組。此頁面也不會寫入持久化存儲。
(3)如果操作系統或PostgreSQL服務器因電源故障等原因而失敗,則所有插入的數據都將丟失。
因此在系統故障的時候,沒有WAL的數據庫系統是很脆弱的。

9.1.2 插入操作和數據庫恢復

爲了在不影響性能的情況下處理上述系統故障,PostgreSQL支持WAL。在本小節中,描述了一些關鍵字和關鍵概念,然後描述了WAL數據的寫入和數據庫的恢復。

PostgreSQL將所有修改作爲歷史數據寫入持久存儲,以準備故障的時候使用。在PostgreSQL中,歷史數據稱爲XLOG記錄或WAL數據。

XLOG記錄通過更改操作(如插入,刪除或提交操作)寫入內存中的WAL緩衝區。當事務提交/中止時,它們立即把WAL段文件寫入到磁盤上。 (嚴格地說,在其他情況下也可能會寫入XLOG記錄。詳細信息將在第9.5節中描述。​​)XLOG記錄的LSN(日誌序列號)表示其記錄在事務日誌中寫入的位置。LSN號作爲XLOG記錄唯一ID。

順便說一句,當我們考慮數據庫系統如何恢復時,可能會有一個問題; PostgreSQL從什麼時候開始恢復?答案是REDO point;也就是說,REDO point是在最新的檢查點啓動時寫入XLOG記錄的位置(PostgreSQL中的檢查點在第9.7節中描述)。事實上,數據庫恢復處理與檢查點處理密切相關,這兩種處理都是不可分割的。

由於主要關鍵詞和概念的介紹剛剛完成,從現在開始將用WAL描述元組插入。參見圖9.2和以下描述。 (另請參閱此幻燈片。)
圖9.2 有WAL的插入操作
在這裏插入圖片描述
'TABLE_A的LSN’是表TABLE_A的頁頭中顯示pd_lsn的值。 'page的LSN’是一樣的。

(1)檢查點的後臺進程,定期執行檢查點。每當checkpointer啓動時,它會將名爲checkpoint record的XLOG記錄寫入當前WAL段。此記錄包含最新REDO點的位置。
(2)發出第一個INSERT語句,PostgreSQL將TABLE_A的頁面加載到共享緩衝池中,將一個元組插入頁面,創建該語句的XLOG記錄並將其寫入LSN_1位置的WAL緩衝區,並且更新TABLE_A的LSN從LSN_0到LSN_1。
在此示例中,此XLOG記錄是頭數據和整個元組。
(3)當此事務提交時,PostgreSQL創建並將此提交操作的XLOG記錄寫入WAL緩衝區,然後將WAL緩衝區中的所有XLOG記錄刷新到LSN_1的WAL段文件中。
(4)發出第二個INSERT語句,PostgreSQL在頁面中插入一個新元組,創建該元組的XLOG記錄並將其寫入LSN_2的WAL緩衝區,並將TABLE_A的LSN從LSN_1更新爲LSN_2。
(5)當該語句的事務提交時,PostgreSQL以與步驟(3)相同的方式操作。
(6)想象一下操作系統何時發生故障。即使共享緩衝池中的所有數據都丟失,頁面的所有修改都已作爲歷史數據寫入WAL段文件。

以下將說明如何將數據庫集羣恢復到崩潰之前的狀態。沒有必要做任何特殊的事情,因爲PostgreSQL會通過重啓自動進入恢復模式。見圖9.3(和此幻燈片)。 PostgreSQL按順序將從REDO點讀取和重放相應WAL段文件中的XLOG記錄。

圖9.3 使用WAL做數據恢復
在這裏插入圖片描述
(1)PostgreSQL從相應的WAL段文件中讀取第一個INSERT語句的XLOG記錄,將TABLE_A的頁面從數據庫集羣加載到共享緩衝池中。

(2)在嘗試重放XLOG記錄之前,PostgreSQL將XLOG記錄的LSN與相應頁面的LSN進行比較,這樣做的原因將在第9.8節中描述。重放XLOG記錄的規則如下所示。
如果XLOG記錄的LSN大於頁面的LSN,則XLOG記錄的數據部分將插入頁面,頁面的LSN將更新爲XLOG記錄的LSN。另一方面,如果XLOG記錄的LSN較小,除了讀取下一個WAL數據之外沒有其他任何操作。
在此示例中,XLOG記錄被重放,因爲XLOG記錄的LSN(LSN_1)大於TABLE_A的LSN(LSN_0);然後,TABLE_A的LSN從LSN_0更新爲LSN_1。

(3)PostgreSQL以相同的方式重放剩餘的XLOG記錄。

PostgreSQL可以通過以時間順序重放在WAL段文件中寫入的XLOG記錄來以這種方式恢復自身。因此,PostgreSQL的XLOG記錄顯然是REDO日誌。

雖然寫XLOG日誌會花費一定的代價,但與寫整個修改過的頁面相比就不算什麼了。我們相信,我們可以獲得了更大的好處,比如系統故障的容忍度。

9.1.3 整頁寫

假設因爲後臺進程寫入髒頁時由於操作系統原因而失敗,TABLE_A的存儲的頁面數據損壞。由於XLOG記錄無法在損壞的頁面上重播,因此我們需要一個額外附加的功能。

PostgreSQL支持一種稱爲整頁寫入的功能來處理此類故障。如果啓用,PostgreSQL會在每個檢查點之後,將每個頁面第一次更改期間寫頭數據和整個頁面作爲XLOG記錄;默認已啓用。在PostgreSQL中,包含整個頁面的這種XLOG記錄稱爲備份塊(或full-page image)。

在啓用了整頁寫入的情況下,讓我們再次描述元組的插入。參見圖9.4和以下描述。
圖9.4 整頁寫
在這裏插入圖片描述
(1)檢查點啓動一個檢查點進程。
(2)在第一個INSERT語句插入時,雖然PostgreSQL和前一個小節中的操作幾乎相同,但此XLOG記錄的是整個頁面的備份塊(即它包含整個頁面),因爲這是在最新的檢查點之後首次寫這個頁面。
(3)當此事務提交時,PostgreSQL的操作方式與前一小節相同。
(4)在第二個INSERT語句插入時,PostgreSQL的操作方式與上一小節相同,因爲此XLOG記錄不是備份塊(不包含整個頁面)。
(5)當該語句的事務提交時,PostgreSQL的操作方式與前一小節相同。
(6)爲了演示整頁寫入的有效性,這裏我們考慮這樣的情況:當後臺寫入進程將其寫入HDD時發生操作系統故障,磁盤上TABLE_A的頁面已被破壞。

重新啓動PostgreSQL服務以修復損壞的頁面。參見圖9.5和以下描述。

圖9.5. 使用備份塊恢復數據庫
在這裏插入圖片描述
(1)PostgreSQL讀取第一個INSERT語句的XLOG記錄,並將損壞的TABLE_A頁面從數據庫集羣加載到共享緩衝池中。在此示例中,XLOG記錄是備份塊,因爲根據整頁寫入的寫入規則,每個頁面的第一個XLOG記錄始終是備份塊。
(2)當XLOG記錄是備份塊時,則會使用另一個重放規則:記錄的數據部分(即頁面本身)將被覆蓋到頁面上,而不管兩個LSN的值如何,並且頁面的LSN更新到XLOG記錄的LSN。
在此示例中,PostgreSQL將記錄的數據部分覆蓋到損壞的頁面,並將TABLE_A的LSN更新爲LSN_1。通過這種方式,損壞的頁面由其備份塊恢復。
(3)由於第二個XLOG記錄是非備份塊,因此PostgreSQL的操作方式與前一小節中的操作方式相同。

即使發生了一些數據寫入失敗,也可以恢復PostgreSQL。 (當然,如果發生文件系統或介質故障,則不適用。)

9.2 事物日誌和WAL文件

從邏輯上講,PostgreSQL將XLOG記錄寫入事務日誌,是一個8字節長的虛擬文件(16 ExaByte)。

由於產生事務日誌實際上是無限的,因此可以說8字節地址空間足夠大,我們不可能處理容量爲8字節長度的文件。因此,在PostgreSQL中的事務日誌默認切分爲16 MB的文件,每個文件稱爲WAL段。見圖9.6。

WAL文件大小
在版本11以後,當通過initdb命令創建PostgreSQL集羣時,可以使用–wal-segsize選項配置WAL段文件的大小。

圖9.6 事物日誌和WAL文件
在這裏插入圖片描述
WAL文件名爲十六進制24位數字,命名規則如下:
在這裏插入圖片描述

timelineId
PostgreSQL的WAL包含timelineId(4字節無符號整數)的概念,將在第10章時間點恢復(PITR)中詳細介紹。所以,本章中timelineId固定爲0x00000001,因爲這個概念在以下的介紹說明中不是必需的。

第一個WAL段文件是000000010000000000000001.如果第一個已經寫滿了XLOG記錄,則提供第二個000000010000000000000002。後續文件連續按升序使用,直到填寫0000000100000000000000FF之後,才提供下一個000000010000000100000000。這樣,每當最後兩位數字結轉時,中間的8位數字就會增加一位。

同樣,在0000000100000001000000FF填滿後,將會寫入000000010000000200000000,依此類推。

pg_xlogfile_name / pg_walfile_name
使用內置函數pg_xlogfile_name(9.6版本及之前)或pg_walfile_name(10版本及之後),我們可以找到包含指定LSN的WAL文件名。一個例子如下所示:
在這裏插入圖片描述

9.3 WAL的內部結構

默認情況下,WAL段是一個16 MB的文件,內部切分爲8192字節(8 KB)的頁面。第一頁是由結構體XLogLongPageHeaderData定義的頭數據,而所有其他頁的標題具有由結構體XLogPageHeaderData定義的頁面信息。在頁頭之後,XLOG記錄從頭開始按降序寫入每個頁面。見圖9.7。

圖9.7 WAL文件的內部結構
在這裏插入圖片描述
XLogLongPageHeaderData結構和XLogPageHeaderData結構在src/include/access/xlog_internal.h中定義。這裏省略了對這兩種結構的說明,因爲在以下描述中不需要這些結構。

XLogLongPageHeaderData的定義
在這裏插入圖片描述
XLogPageHeaderData的定義
在這裏插入圖片描述

9.4 XLOG記錄的內部結構

XLOG記錄包括頭部以及相關的數據部分。第一小節描述了頭部結構;其餘兩個小節分別解釋版本9.4或更早版本以及版本9.5中的數據部分的結構。 (版本9.5中的數據格式已更改。)

9.4.1 XLOG記錄的頭部

所有XLOG記錄都有一個由結構XLogRecord定義的通用頭部分。這裏,9.4版本以及之前的結構如下所示,但是它在9.5版中已更改。
在這裏插入圖片描述
除了兩個變量之外,大多數變量都很好理解,不需要描述。

xl_rmid和xl_info都是與資源管理器相關的變量,資源管理器是與WAL功能相關的操作的集合,例如寫入和重放XLOG記錄。每個PostgreSQL版本的資源管理器數量往往會增加,10版本包含以下內容:
在這裏插入圖片描述
以下是資源管理器如何在各方面工作的一些代表性示例:

  • 如果發出INSERT語句,則其XLOG記錄的頭變量xl_rmid和xl_info分別設置爲“RM_HEAP”和“XLOG_HEAP_INSERT”。恢復數據庫集羣時,根據xl_info選擇的RM_HEAP的函數heap_xlog_insert()重放此XLOG記錄。
  • 雖然它與UPDATE語句類似,但XLOG記錄的頭變量xl_info設置爲’XLOG_HEAP_UPDATE’,並且RM_HEAP的函數heap_xlog_update()在數據庫恢復時重放記錄。
  • 當事務提交時,其XLOG記錄的頭變量xl_rmid和xl_info分別設置爲“RM_XACT”和“XLOG_XACT_COMMIT”。恢復數據庫集羣時,函數xact_redo_commit()將重放此記錄。

在9.5或更高版本中,已從XLogRecord中移除了一個變量(xl_len)以優化XLOG記錄格式,從而減少了幾個字節的大小。

XLogRecord的定義如下:
在這裏插入圖片描述

版本9.4或更早版本中的XLogRecord結構在src/include/access/xlog.h中定義,版本9.5或更高版本中的XLogRecord結構在src/include/access/xlogrecord.h中定義。
heap_xlog_insert和heap_xlog_update在src/backend/access/heap/heapam.c中定義;函數xact_redo_commit在src/backend/access/transam/xact.c中定義。

9.4.2 XLOG記錄的數據部分(9.4版本以及之前的版本)

XLOG記錄的數據部分分爲備份塊(整頁)或非備份塊(操作的不同數據)。

圖9.8 XLOG記錄實例(9.4版本以及更早的版本)
在這裏插入圖片描述
下面使用一些具體示例描述XLOG記錄的內部佈局。

9.4.2.1 備份塊

備份塊如圖9.8(a)所示。它由兩個數據結構和一個數據對象組成,如下所示:

  1. XLogRecord結構(頭部)
  2. BkpBlock結構
  3. 整個頁面,除了空閒空間

BkpBlock包含用於在數據庫集羣中標識此頁面的變量(即relfilenode和包含此頁面的關係的fork編號,以及此頁面的塊編號),以及此頁面的可用空間的起始位置和長度。
BkpBlock定義如下:
在這裏插入圖片描述

9.4.2.2 非備份塊

在非備份塊中,數據部分的佈局根據每個操作而不同。這裏,INSERT語句的XLOG記錄作爲代表性示例來解釋。見圖9.8(b)。在這種情況下,INSERT語句的XLOG記錄由兩個數據結構和一個數據對象組成,如下所示:

  1. XLogRecord結構(頭部)
  2. xl_heap_insert結構
  3. 插入的元組 (嚴格地說,從元組中刪除了幾個字節)

結構xl_heap_insert包含在數據庫集羣中標識插入的元組的變量(即包含此元組的表的relfilenode,以及此元組的tid),以及此元組的可見性標誌。

xl_heap_insert定義如下:
在這裏插入圖片描述

從結構體xl_heap_header的源代碼註釋中描述了從插入的元組中刪除幾個字節的原因:
在WAL中我們不會存儲插入或更新元組的整個固定的部分(HeapTupleHeaderData);我們可以通過重建字段來節省一些字節,這些字節可以在WAL中的其他地方找到,或者不需要重建。

這裏展示的另一個例子。見圖9.8(c)。檢查點的XLOG記錄非常簡單;它由兩個數據結構組成,如下所示:

  1. XLogRecord結構(頭部分)
  2. 檢查點結構,其中包含其檢查點信息(請參閱第9.7節中的更多詳細信息)

xl_heap_header結構在src/include/access/htup.h中定義,而CheckPoint結構在src/include/catalog/pg_control.h中定義。

9.4.3 XLOG記錄的數據部分(9.5版本以及以後的版本)

在9.4或更早版本中,沒有XLOG記錄的通用格式,因此每個資源管理器必須定義一個自己的格式。在這種情況下,維護源代碼和實現與WAL相關的新功能變得越來越困難。爲了解決這個問題,9.5版本中引入了一種不依賴於資源管理器的通用的結構化格式。

XLOG記錄的數據部分可以分爲兩部分:頭部分和數據部分。見圖9.9。

圖9.9 一般的XLOG記錄格式
在這裏插入圖片描述

頭部分包含零個或多個XLogRecordBlockHeaders和零個或一個XLogRecordDataHeaderShort(或XLogRecordDataHeaderLong);但必須至少包含其中一個。當記錄存儲整頁(即備份塊)時,XLogRecordBlockHeader包括XLogRecordBlockImageHeader,並且如果其塊被壓縮,則還包括XLogRecordBlockCompressHeader。

數據部分由零個或多個塊數據和零個或一個主數據組成,它們分別對應於XLogRecordBlockHeader和XLogRecordDataHeader。

WAL壓縮
在9.5或更高版本中,通過設置參數wal_compression = enable,可以使用LZ壓縮方法壓縮XLOG記錄中的整個頁。在這種情況下,將添加結構體XLogRecordBlockCompressHeader。
該特徵有兩個優點和一個缺點。優點是降低了寫入記錄的I / O成本並抑制了WAL段文件的消耗。缺點是需要消耗大量CPU資源才能進行壓縮。

XLogRecordBlockHeaders定義如下:
在這裏插入圖片描述
XLogRecordDataHeaderShort定義如下:
在這裏插入圖片描述
XLogRecordBlockImageHeader定義如下:
在這裏插入圖片描述
XLogRecordBlockCompressHeader定義如下:
在這裏插入圖片描述
圖9.10 XLOG記錄實例(9.5版本以及以後的版本)
在這裏插入圖片描述

9.4.3.1 備份塊

INSERT語句創建的備份塊如圖9.10(a)所示。它由四個數據結構體和一個數據對象組成,如下所示:

  1. 結構體XLogRecord(頭部分)
  2. 結構體XLogRecordBlockHeader包括一個LogRecordBlockImageHeader
  3. 結構體XLogRecordDataHeaderShort
  4. 備份塊(塊數據)
  5. 結構體xl_heap_insert(主數據)

XLogRecordBlockHeader包含用於標識數據庫集羣中的塊的變量(relfilenode,fork編號和塊編號); XLogRecordImageHeader包含此塊的長度和偏移號。 (這兩個頭結構一起存儲的數據和在版本9.4之前使用的BkpBlock的數據相同。)

XLogRecordDataHeaderShort存儲xl_heap_insert結構的長度,該結構是記錄的主要數據。 (見下文。)

除了在某些特殊情況下(例如,在邏輯解碼和推測性插入中),一般不使用包含整頁圖像的XLOG記錄的主要數據。當這個記錄重放時會忽略它,這是冗餘數據。它可能在未來得到改善。
此外,備份塊記錄的主要數據取決於創建它們的語句。例如,UPDATE語句附加xl_heap_lock或xl_heap_updated。

9.4.3.2 非備份塊

接下來,INSERT語句創建的非備份塊記錄將描述如下(也參見圖9.10(b))。它由四個數據結構和一個數據對象組成,如下所示:

  1. 結構體XLogRecord(頭部分)
  2. 結構體XLogRecordBlockHeader
  3. 結構體XLogRecordDataHeaderShort
  4. 一個插入的元組(嚴格地說,一個xl_heap_header結構和一個插入的整個數據)
  5. 結構xl_heap_insert(主要數據)

XLogRecordBlockHeader包含三個值(relfilenode,fork編號和塊編號),用於指定插入元組的塊,以及插入元組的數據部分的長度。 XLogRecordDataHeaderShort包含新xl_heap_insert結構的長度,該結構是此記錄的主要數據。
新的xl_heap_insert只包含兩個值:塊內該元組的偏移量,以及可見性標誌;它變得非常簡單,因爲XLogRecordBlockHeader存儲了舊數據中包含的大部分數據。

最後一個例子,檢查點記錄如圖9.10(c)所示。它由三個數據結構組成,如下所示:

  1. 結構體XLogRecord(頭部分)
  2. 結構體XLogRecordDataHeaderShort包含主數據長度
  3. 結構體CheckPoint(主要數據)

結構xl_heap_header在src/include/access/htup.h中定義,CheckPoint結構在src/include/catalog/pg_control.h中定義。

儘管新格式對我們來說有點複雜,但它是爲資源管理器的解析器設計的,並且XLOG記錄的許多類型的尺寸通常小於老的結構定義。主結構的尺寸如圖9.8和9.10所示。所以你可以計算這些記錄的大小並相互比較。 (新檢查點的大小大於老版本的,但它包含更多變量。)

xl_head_insert定義如下:
在這裏插入圖片描述

9.5 XLOG記錄寫入

我們完成了熱身練習,現在我們已經準備好了解XLOG記錄的寫入。我將在本節中儘可能準確地解釋它。
首先,執行以下語句來探索PostgreSQL內部:
testdb=# INSERT INTO tbl VALUES ('A');
通過發出上述語句,調用內部函數exec_simple_query(); exec_simple_query()的僞代碼如下所示:
在這裏插入圖片描述
在以下段落中,將解釋僞代碼的每一行以理解XLOG記錄的寫入;另見圖 9.11和9.12。

(1)函數ExtendCLOG()將此事務的狀態“IN_PROGRESS”寫入(內存中)CLOG中。
(2)函數heap_insert()將堆元組插入共享緩衝池的目標頁面,創建該頁面的XLOG記錄,並調用函數XLogInsert()。
(3)函數XLogInsert()將heap_insert()創建的XLOG記錄寫入LSN_1的WAL緩衝區,然後將修改後的頁面的pd_lsn從LSN_0更新爲LSN_1。
(4)調用函數finish_xact_command()提交事務,並創建此提交操作的XLOG記錄,然後函數XLogInsert()將此記錄寫入LSN_2的WAL緩衝區。

圖9.11 XLOG記錄的寫入順序
上圖XLOG記錄的格式是9.4版本的

上圖XLOG記錄的格式是9.4版本的

(5)函數XLogWrite()將WAL緩衝區上的所有XLOG記錄刷新到WAL段文件。
如果參數wal_sync_method設置爲“open_sync”或“open_datasync”,則同步寫入記錄,因爲會使用指定標誌O_SYNC或O_DSYNC的open()函數通過系統調用寫入所有記錄。如果參數設置爲’fsync’,‘fsync_writethrough’或’fdatasync’,則系統將調用相應的函數 - 帶有F_FULLFSYNC選項的fsync(),fcntl()或fdatasync()。所以在任何情況下,都會確保將所有XLOG記錄寫入存儲。
(6)函數TransactionIdCommitTree()將此事務的狀態從“IN_PROGRESS”更改爲“COMMITTED”。

圖9.12 接圖9.11 XLOG記錄的寫入順序
在這裏插入圖片描述
在上面的示例中,commit動作導致將XLOG記錄寫入WAL段,但是當發生以下任何一種情況時,會發生寫入操作:

  1. 一個正在運行的事務已提交或已中止。
  2. WAL緩衝區已經填滿了許多元組。 (WAL緩衝區大小設置爲參數wal_buffers。)
  3. WAL寫進程定期寫入。 (見下一節。)

如果出現上述情況之一,WAL緩衝區上的所有WAL記錄都將寫入WAL段文件,無論它們的事務是否已提交。

當然,DML(數據操作語言)操作會寫XLOG記錄,但非DML操作也是如此。如上所述,提交操作會寫入包含已提交事務的id的XLOG記錄。另一個例子是檢查點會將該檢查點的一般信息寫入到XLOG記錄。此外,SELECT語句在特殊情況下也會創建XLOG記錄,但一般情況下不會創建XLOG。例如,如果在SELECT語句處理期間刪除了不必要的元組,而且在頁面中發生了由HOT(Heap Only Tuple)引發的碎片整理,則修改頁面的XLOG記錄將寫入WAL緩衝區。

9.6 WAL寫進程

WAL寫進程是一個後臺進程,用於定期檢查WAL緩衝區並將所有未寫入的XLOG記錄寫入WAL段。此過程的目的是避免爆發性的寫XLOG記錄。如果此進程沒有啓用,則在提交大量數據時,寫入XLOG記錄可能會遇到瓶頸。

WAL寫進程默認工作的,並且無法禁用。檢查間隔設置爲配置參數wal_writer_delay,默認值爲200毫秒。

9.7 Postgresql中的檢查點進程

在PostgreSQL中,checkpointer(後臺)進程執行檢查點;當下列之一發生時,其進程開始:

  1. 從前一個檢查點發生過後的時間超過checkpoint_timeout設置的間隔(默認間隔爲300秒(5分鐘))。
  2. 在版本9.4或更早版本中,自上一個檢查點以來,超過使用checkpoint_segments(默認數量爲3)設置的WAL段文件數量。
  3. 在9.5或更高版本中,pg_xlog(版本10或更高版本,pg_wal)中WAL段文件的總大小已超過參數max_wal_size的值(默認值爲1GB(64個文件))。
  4. PostgreSQL服務在smart和fast模式下停止。

當超級用戶手動發出CHECKPOINT命令時,也會執行檢查點。

在版本9.1或更早版本中,如第8.6章節所述,後臺寫器進程同時執行checkpointing和髒頁寫。

在以下章節中,將描述檢查點的概述和保存當前檢查點的元數據的pg_control文件。

9.7.1 檢查點進程概要

檢查點進程有兩個方面:爲數據庫恢復做準備和共享緩衝池上的髒頁清除。在本小節中,將着重於前一個處理來描述其內部處理。參見圖9.13和以下描述。
圖9.13 Postgresql 檢查點的內部處理過程
在這裏插入圖片描述

(1)檢查點開始後,REDO點存儲在內存中; REDO點是在最新檢查點啓動時寫入XLOG記錄的位置,並且是數據庫恢復的起點。
(2)將此檢查點的XLOG記錄(即檢查點記錄)寫入WAL緩衝區。記錄的數據部分由結構CheckPoint定義,結構包含幾個變量,例如存儲在步驟(1)中的REDO點。
此外,寫入檢查點記錄的位置實際上稱爲檢查點。
(3)共享緩衝池中的所有數據(例如,CLOG的內容等)被刷新到磁盤。
(4)共享緩衝池中的所有髒頁都會逐漸寫入並刷新到存磁盤。
(5)更新pg_control文件。此文件包含一些基本信息,例如檢查點記錄所寫的位置(a.k.a.檢查點位置)。稍後詳細描述此文件。

checkpoint結構定義:
在這裏插入圖片描述

爲了從數據庫恢復的角度總結上述描述,檢查點創建包含REDO點的檢查點記錄,並將檢查點位置和更多內容存儲到pg_control文件中。因此,PostgreSQL可以通過從pg_control文件提供的REDO點(從檢查點記錄獲得)重放WAL數據來恢復自身。

9.7.2 pg_control文件

由於pg_control文件包含檢查點的基本信息,因此它對於數據庫恢復肯定是必不可少的。如果它被破壞或不可讀,則恢復過程無法啓動以便無法獲得起點。

即使pg_control文件存儲了40多個項目,但是下一節中僅需要三個項目,如下所示:

  • State - 最新檢查點開始時數據庫服務的狀態。共有七個狀態:“start up”是系統啓動的狀態; 'shutdown’是系統出關閉命令正常關閉的狀態; “in production”是系統運行的狀態;等等。
  • 最新檢查點位置 - LSN最新檢查點記錄的位置。
  • 先前檢查點位置 - LSN前一檢查點記錄的位置。請注意,它在版本11中已棄用;細節描述如下。

pg_control文件存儲在global子目錄中;可以使用pg_controldata實用程序顯示其內容。
在這裏插入圖片描述

PostgreSQL 11中將會刪除先前檢查點
PostgreSQL 11或更高版本只存儲包含最新或更新的檢查點的WAL段;將不會存儲包含先前檢查點的舊的段文件,這樣做爲了以減少用於在pg_xlog(pg_wal)子目錄下保存WAL段文件的磁盤空間。詳細瞭解此主題

9.8 Postgresql中數據庫恢復

PostgreSQL實現了基於重做日誌的恢復功能。如果數據庫服務器崩潰,PostgreSQL通過從REDO點順序重放WAL段文件中的XLOG記錄來恢復數據庫集羣。

在本節之前我們已經多次討論了數據庫恢復,因此我將描述有關恢復的兩個方面,這些內容尚未解釋。

第一點是PostgreSQL如何開始恢復處理。當PostgreSQL啓動時,它首先讀取pg_control文件。以下是從那個時間點開始恢復處理的細節。參見圖9.14和以下描述。

9.14 恢復處理描述
在這裏插入圖片描述
(1)PostgreSQL在啓動時讀取pg_control文件的信息。如果狀態項處於’in production’,PostgreSQL將進入恢復模式,因爲這意味着數據庫沒有正常停止;如果’shutdown’,它將進入正常的啓動模式。
(2)PostgreSQL從相應的WAL段文件中讀取最新的檢查點記錄,該記錄位於pg_control文件中,並從記錄中獲取REDO點。如果最新的檢查點記錄無效,PostgreSQL將讀取前一個檢查點的記錄。如果兩個記錄都沒有讀取到,將自行放棄恢復。 (請注意,先前的檢查點在從PostgreSQL 11中不會再存儲。)
(3)適當的資源管理器從REDO點開始按順序讀取和重放XLOG記錄,直到它們到達最新WAL段的最後一個點。當重放XLOG記錄並且它是備份塊時,無論其LSN如何,它都將覆蓋相應表的頁面。否則,僅當此記錄的LSN大於相應頁面的pd_lsn時,纔會重放(非備份塊)XLOG記錄。

第二點是關於LSN的比較:爲什麼應該比較非備份塊的LSN和相應頁面的pd_lsn。與前面的示例不同,這裏將使用強調兩個LSN之間進行比較的特定示例來解釋。見圖。 9.15和9.16。 (注意,省略了WAL緩衝區以簡化描述。)

圖9.15 後臺寫進程工作期間的插入操作
在這裏插入圖片描述

(1)PostgreSQL將一個元組插入TABLE_A,並在LSN_1處寫入一條XLOG記錄。
(2)後臺寫進程將TABLE_A的頁面寫入磁盤。此時,此頁面的pd_lsn爲LSN_1。
(3)PostgreSQL在TABLE_A中插入一個新元組,並在LSN_2處寫入一條XLOG記錄。修改後的頁面還沒有寫入磁盤。

與概述中的示例不同,在這種情況下,TABLE_A的頁面已寫入磁盤。

使用immediate模式關閉,然後啓動。

圖9.16 數據庫恢復
在這裏插入圖片描述
(1)PostgreSQL加載第一個XLOG記錄和TABLE_A的頁面,但不重放它,因爲該記錄的LSN不大於TABLE_A的LSN(兩個值都是LSN_1)。
(2)接下來,PostgreSQL重放第二個XLOG記錄,因爲該記錄的LSN(LSN_2)大於當前TABLE_A的LSN(LSN_1)。

從此示例中可以看出,如果非備份塊的重放順序不正確或者多次重放非備份塊,則數據庫集羣將不一致。簡而言之,非備份塊的重做(重放)操作不是冪等的。因此,爲了保留正確的重放順序,當且僅當其LSN大於相應頁面的pd_lsn時,才重放非備份塊記錄。

另一方面,由於備份塊的重做操作是冪等的,因此備份塊可以重放任意次,而不管其LSN如何。

9.9 WAL段文件切換

PostgreSQL將XLOG記錄寫入存儲在pg_xlog子目錄(版本10或更高版本,pg_wal子目錄)中的一個WAL段文件中,如果已填滿舊文件,則切換爲新文件。 WAL文件的數量根據服務端幾個參數的配置而有所不同。此外,他們的管理策略在9.5版本中得到了改進。

在以下小節中,描述了WAL段文件的切換和管理。

9.9.1 WAL段的切換

發生以下任一情況時,會發生WAL段切換:

  1. WAL段已經填滿。
  2. 發出pg_switch_xlog
  3. archive_mode已啓用,並超出archive_timeout設置的時間。

切換後的段文件通常被回收(重命名和重用)以供將來使用,如果沒有必要,也會刪除掉。

9.9.2 9.5版本以及更新的版本中的WAL段管理

每當檢查點啓動時,PostgreSQL都會預估和準備下一個檢查點週期所需的WAL段文件數。這種預估是參考前一個檢查點週期中消耗的文件數量。它們從前一個檢點點包含REDO點的段做統計,並且該值在min_wal_size(默認情況下,80 MB,即5個文件)和max_wal_size(1 GB,即64個文件)之間。如果檢查點啓動,將保留或回收必要的文件,同時刪除不必要的文件。

具體例子如圖9.17所示。假設在檢查點啓動之前有六個文件,WAL_3包含先前的REDO點(在版本10或更早版本中是先前的REDO點;在版本11或更高版本中,是最新REDO點),並且PostgreSQL預估需要五個文件。在這種情況下,WAL_1將重命名爲WAL_7以進行回收,並且將刪除WAL_2。

比前一個REDO點更老的的文件會被刪除,因爲從第9.8節中描述的恢復機制可以清楚地看出,它們永遠不會被使用。

圖9.17 檢查點回收和刪除WAL段文件
在這裏插入圖片描述
如果由於WAL活動激增而需要更多文件,則在WAL文件的總大小小於max_wal_size時將創建新文件。例如,在圖9.18中,如果已填充WAL_7,則新創建WAL_8。

圖9.18 創建WAL段文件
在這裏插入圖片描述
WAL文件的數量會根據數據庫的繁忙程度自適應地改變。如果WAL數據寫入的數量不斷增加,則WAL段文件的預估數量以及WAL文件的總大小也逐漸增加。相反的情況下(即WAL數據寫入量減少),這些減少。

如果WAL文件的總大小超過max_wal_size,則將啓動檢查點。圖9.19說明了這種情況。通過檢查點,將創建一個新的REDO點,之前最新的REDO點變爲了前一個REDO點;然後將回收不必要的舊文件。通過這種方式,PostgreSQL將始終只保存數據庫恢復所需的WAL段文件。

圖9.19 檢查並回收WAL段文件
在這裏插入圖片描述
配置參數wal_keep_segments複製槽功能也會影響WAL段文件的數量。

9.9.3 9.4版本以及之前的版本WAL段管理

WAL段文件的數量主要由以下三個參數控制:checkpoint_segments,checkpoint_completion_target和wal_keep_segments。它的數量通常大於(
(2+checkpoint_completion_target)×checkpoint_segments+1)或
(checkpoint_segments+wal_keep_segments+1),也可以因數據庫的繁忙程度暫時達到(3×checkpoint_segments+1),複製槽也會影響他們的數量。

如第9.7節所述,檢查點進程會在消耗了checkpoint_segments文件的數量時發生。因此,爲了保證WAL文件中始終包含兩個或更多REDO點,所以文件的數量始終大於2×CHECKPOINT_SEGMENTS。如果發生超時,情況也是如此。因此,PostgreSQL始終保存足夠的WAL段文件(有時超過必要數量)以進行恢復。

在版本9.4或更早版本中,設置參數checkpoint_segments都會很糾結。如果設置爲較小的數字,則檢查點頻繁發生,這會導致性能下降,而如果設置的太大,則WAL文件會佔用巨大的磁盤空間,但是其中一些並不是必需的,所以會造成空間浪費。
在9.5版中,WAL文件的管理策略已得到改進,checkpoint_segments參數已經廢棄。因此,上述權衡問題也已得到解決。

9.10 持續歸檔和歸檔日誌

持續歸檔是postgresql的一個功能,可在WAL段切換時將WAL段文件複製到歸檔區域,由歸檔(後臺)進程執行。複製的文件稱爲歸檔日誌。這項功能通常用於第10章中描述的物理熱備份和PITR(時間點恢復)。

歸檔區域的路徑設置爲配置參數archive_command。例如,使用以下參數,每當段切換時,WAL段文件都會複製到目錄’/home/postgres/archives/’:

archive_command = 'cp %p /home/postgres/archives/%f'
其中,%p被複制WAL段目錄的佔位符,%f是歸檔日誌文件的佔位符。

9.20 持續歸檔
在這裏插入圖片描述

在切換WAL段文件WAL_7時,WAL_7作爲archive log 7複製到歸檔區域。

參數archive_command可以使用任何Unix命令和工具設置,因此您可以通過scp命令將歸檔日誌傳輸到其他主機,也可以使用文件備份工具替代普通的複製命令傳輸至其他主機。

PostgreSQL不會自動清理已經創建的歸檔日誌,因此打開歸檔時應妥善管理日誌。如果你什麼都不做,歸檔日誌的數量將持續增加。

pg_archivecleanup是歸檔日誌有效的管理工具之一。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章