在客戶中,經常會遇到由於大量的WAL段占據了WAL目錄(pg_wal目錄),導致磁盤空間使用量突然暴增的案例。慌張的客戶通常會問:“為什么PostgreSQL不刪除它們呢?”
我們發現最常見的原因是:
1.WAL歸檔失敗
2.復制槽正在持有舊的WAL
然而,近些年來,在與此類似的主題下,出現了另一種不同類型的案例。很顯然,“慢”是帶有主觀性的,而且大多數用戶會將“慢”與WAL段的生成速度相比。這種案例的增加主要是由於單台服務器的處理能力的提高,PostgreSQL的可擴展性不斷提高(例如分區功能的最新改進,批量數據加載的改進等)以及更快的新一代存儲的使用而引起的。基本上,單台服務器能做的工作越來越多。因此,大量的WAL被生成也逐漸變成常規現象。這一點,與為什么WAL壓縮正在成為迫切需求的原因是一樣的(參考原文:https://www.percona.com/blog/2020/02/13/compression-of-postgresql-wal-archives-becoming-more-important/)。像WAL-G和pgBackRest這樣的備份解決方案如何通過內置的壓縮特性解決這個問題也在那篇博客文章中討論了。
同時,由於相比於昂貴的備份設備的價格優勢和經過時間驗證的可靠性,遠程雲存儲正成為存儲歸檔WAL的更具吸引力的選擇。而且,用戶/組織對雲存儲也越來越熟悉,舒適度的提高是決策使用雲存儲做備份的主要驅動力。
但是,這種WAL段的快速生成,結合使用緩慢/遠程的存儲作為歸檔存放位置是整個WAL歸檔過程的致命組合。除非進行適當的監控和處理,否則可能會導致災難。
在本文中,我們將詳細了解歸檔進程如何工作以及如何以同步方式處理archive_command中指定的外部shell命令。 此外,我們將嘗試查看在WAL的同步歸檔處理過程中的特定區域,以及它如何影響歸檔速度並成為一項挑戰的。
同步的WAL歸檔
PostgresSQL的WAL歸檔非常靈活,因為它可以通過為參數archive_command指定外部的shell命令。這種特性可以執行上一篇文章中討論的任何自定義歸檔腳本。讓我們看一下WAL歸檔是如何開始的。
通常,歸檔的整個事件鏈都是WAL寫(XLogWrite())的一部分。當一個WAL段文件完成后,通過將.ready文件插入pg_wal目錄下的archive_status目錄中,通知歸檔進程對這個段有一些工作要處理(內部函數XLogArchiveNotifySeg-> XLogArchiveNotify)。例如,如果要歸檔的WAL段是0000000100000001000000C6,則.ready文件將是0000000100000001000000C6.ready。該.ready文件充當歸檔進程的通知文件。除了創建此文件外,還將信號發送到歸檔進程以進行喚醒(將SIGUSR1發送到歸檔進程)。現在,歸檔進程可以醒來並開始處理所有.ready文件。

與歸檔進程的所有直接通信都是通過信號進行的。歸檔進程收到信號(SIGUSR1)后,就知道有一些工作要做了。然后,歸檔進程開始瀏覽后綴為.ready的每個文件,並找到需要復制的最舊的段文件。最早的WAL段應首先歸檔是最重要的,因為:
1.這將在需要執行還原時有所幫助,因為是按照順序應用歸檔的WAL文件。如果兩者之間缺少任何WAL,則可恢復性將受到影響。
2.發生檢查點時,最早的WAL段將有更大的機會被回收。 因此,它丟失的機會更大。
現在,讓我們看一下整個操作中的主要瓶頸。
原因1:
讓我們看第一個問題。一個接一個地找出最舊的WAL段並將其逐個歸檔的方法不是很有效。
對於每次迭代,歸檔進程都需要遍歷完整的.ready文件列表以查找最早的文件。在正常情況下,這不是大問題。但是,在許多高活動性服務器和慢速備份存儲中,我們遇到的歸檔滯后了成千上萬個WAL段文件。在這種情況下,執行目錄列表和遍歷.ready文件變得非常低效,這會降低WAL歸檔的速度,這已經很滯后了。 如果不注意,累積效應會導致更危險的情況。
原因2:
慢的第二個問題從這里開始。一旦確認了段文件,就需要將其歸檔。調用內部函數pgarch_archiveXlog(),它將調用system()系統調用以執行archive_command指定的外部命令/腳本。此命令通常使用2個參數-%p(源段文件的相對路徑)和%f(用於指定源段文件的文件名)。一旦通過system()調用執行了外部Shell命令,便會檢查其返回值以了解執行是否成功(已歸檔WAL)或失敗。基本上,歸檔進程等待外部命令的返回。如果外部腳本由於某種原因而具有執行延遲,則所有延遲將累加起來。
原因3:
如果在執行archive_command時底層系統出現故障/超時,則歸檔程序將再等待一秒鍾再嘗試。因此速度很慢,通過WAN連接的存儲可能要等待更多時間。僅當上一個成功時,WAL段才會被歸檔。一旦外部shell命令成功返回(如上所述,pgarch_archiveXlog()函數已成功),則此.ready通知文件將由歸檔器pgarch_archiveDone()重命名為.done。
這里的常見問題之一是“我們需要編寫腳本來刪除已歸檔的WAL段和.done文件嗎?”答案是否定的。檢查點進程將為你完成這一任務。它將刪除.done和相應的WAL段文件(包括循環使用WAL段文件)。如果archive_staus目錄中剩余任何.ready文件,而相應的WAL段已經被回收或移除,那么這些.ready文件將由歸檔進程自己來刪除。
正如我們所討論的,只有兩種通知狀態-.ready或.done。歸檔沒有“進行中(in-progress)”的通知狀態,如果需要同時進行多個歸檔,則這是必不可少的。這是通過精心設計,它不存在。除非WAL歸檔成功,並且.ready文件重命名為.done,否則我們認為歸檔從未發生過。因此,如果在兩者之間發生任何故障,PostgreSQL將再次嘗試歸檔(有時會重新復制同一文件)。
諸如pgBackRest之類的高級備份解決方案具有異步備份功能,該功能將允許多個后台工作進程進行壓縮和WAL歸檔推送,同時前端將確認反饋給PostgreSQL。我們將在即將發表的博客文章中對此進行介紹。
總結
最近,WAL歸檔的同步操作變得越來越痛苦。對於每個未完成歸檔的WAL,WAL歸檔的整個操作將循環地一個接一個地進行,直到不再有WAL段要歸檔為止。如果WAL段的生成速率超過歸檔速率,則WAL段很可能堆積在pg_wal目錄中,並且隨着.ready文件數量的增加,問題變得更加嚴重。 歸檔進程一旦接收到信號(SIGUSR1),便會喚醒並執行上面討論的所有迭代,並且該過程將一直進行到接收到SIGUSR2。沒有內置機制可以使其異步。但是,由於PostgreSQL使用外部命令/腳本進行歸檔操作,因此靈活的程序/腳本可以將整個同步操作轉換為異步操作。這里強調了需要一種備份工具,該工具可以以異步方式推送WAL歸檔。
