22.3. 在線備份以及即時恢復(PITR)

在任何時候,PostgreSQL 都在集群的資料目錄的 pg_xlog/ 子目錄裡維護著一套預寫日誌(WAL)。 這些日誌記錄著每一次對資料庫的資料文件的修改的細節。這些日誌存在是為了防止崩潰:如果系統崩潰, 資料庫可以透過"重放"上次檢查點以來的日誌記錄以恢復資料庫的完整性。 但是,日誌的存在讓它還可以用於第三種備份資料庫的策略:我們可以組合文件系統備份與 WAL 文件的備份。 如果需要恢復,我們就恢復備份,然後重放備份了的WAL文件,把備份恢復到目前的時間。 這個方法對管理員來說,明顯比以前的方法更複雜,但是有非常明顯的優勢:

和簡單的文件系統備份技術一樣,這個方法只能支援整個資料庫集群的恢復,而不是一個子集。 同樣,它還要求大量的歸檔儲存:基礎備份量可能很大,而且忙碌的系統將生成許多兆需要備份的的 WAL 流量。 但是,它仍然時在需要高可靠性的場合下的最好的備份技術。

要想從在線備份中成功恢復,您需要一套連續的 WAL 歸檔文件,它們最遠回朔到您開始備份的時刻。 因此,要想開始備份,您應該在開始第一次基礎備份之前設置並測試您的步驟。 根據我們討論過的歸檔 WAL 文件的機制。

22.3.1. 設置 WAL 歸檔

抽像來看,一個執行著的 PostgreSQL 系統生成一個無限長的 WAL 日誌序列。 系統實際上把這個序列分隔成 WAL段文件,通常一塊時 16M 字元大 (在製作 PostgreSQL 的時候可以改變其大小)。 這些段文件的名字是數值命名的,這些數值反映他們在抽取出來的 WAL 序列中的位置。 在不適用 WAL 歸檔的時候,系統通常只是建立幾個段文件然後"循環"使用它們, 方法是把不再使用的段文件的名字重命名為更高的段編號。 系統假設那些內容比前一次檢查點更老的段文件是沒用的了,然後就可以循環利用。

在歸檔 WAL 資料的時候,我們希望在每個段文件填充滿之後捕獲之, 並且把這些資料在段文件被循環利用之前保存在某處。根據應用以及可用的硬件的不同, 我們可以有許多不同的方法"把資料保存在某處": 我們可以把段文件拷貝到一個 NFS 裝配的目錄,把它們放到另外一台機器上, 或者把它們寫入磁帶機裡(需要保證您有辦法把文件恢復為原名), 或者把它們打成包,燒錄到 CD 裡,或者是其它的什麼方法。 為了給資料庫管理員提供最大可能性的靈活性,PostgreSQL 試圖不對如何歸檔做任何假設。取而代之的是,PostgreSQL 讓管理員聲明一個 shell 命令執行來拷貝一個完整的段文件到它需要去的地方。 該命令可以簡單得就是一個 cp,或者它可以調用一個複雜的 shell 腳本 — 所有都由管理員決定。

所使用的 shell 命令由配置參數 archive_command 聲明, 它實際上總是放在 postgresql.conf 文件裡的。 在這個字串裡,任何 %p 都被要歸檔的文件的絕對路徑代替,而任何 %f 只是被文件名代替。 如果您需要在命令裡嵌入一個真正的 %,寫 %%。 最簡單的有用命令是類似下面這樣的

archive_command = 'cp -i %p /mnt/server/archivedir/%f </dev/null'

它將把 WAL 段拷貝到目錄 /mnt/server/archivedir。 這個只是一個例子,並非我們建議的方法,可能不能在所有系統上都正確執行。

歸檔命令將在執行 PostgreSQL 伺服器的同一個用戶的權限下執行。 因此被歸檔的 WAL 文件實際上包含您的資料庫裡的所有東西,所以您應該確保自己的歸檔資料不會被別人窺探; 比如,歸檔到一個沒有組或者全局讀權限的目錄裡。

有一點很重要:當且僅當歸檔命令成功時,它才返回零。在得到一個零值結果之後, PostgreSQL 將假設該 WAL 段文件已經成功歸檔, 因此它稍後將被刪除或者被新的資料覆蓋。但是,一個非零值告訴 PostgreSQL 該文件沒有被歸檔; 因此它會週期性的重試直到成功。

歸檔命令通常應該設計成拒絕覆蓋已經存在的歸檔文件。這是一個非常重要的安全特性, 可以在管理員操作失誤(比如把兩個不同的伺服器的輸出發送到同一個歸檔目錄)的時候保持您的歸檔的完整性。 我們建議您首先要測試您準備使用到歸檔命令,以保證它實際上不會覆蓋現有的文件, 並且在這種情況下它返回非零狀態。 我們發現,在這方面, cp -i 在某些平台上是正確的,而在其它平台上是不正確的。 如果選定的命令本身並不能正確處理這個問題,您應該增加一個命令預先探測歸檔文件是否存在。 比如,類似下面的東西。

archive_command = 'test ! -f .../%f && cp %p .../%f'

在幾乎所有的 Unix 變種上都工作正確。

在設計您的歸檔環境都時候,請考慮一下如果歸檔命令不停失敗會發生什麼情況, 因為有些方面要求操作者的干涉,或者是歸檔空間不夠了。 比如,如果您往磁帶機上寫,但是沒有自動換帶機,那麼就有可能發生這種情況; 如果磁帶滿了,那就除非換磁帶,否則啥事也做不了。 您應該確保人和錯誤條件或者人和要求操作員干涉帶錯誤都會正確報告, 這樣才能迅速解決這些問題。否則 pg_xlog/ 目錄會不停地填充 WAL 段文件, 直到問題解決。

歸檔命令的速度並不要緊,只要它能跟上您的伺服器生成 WAL 資料的平均速度即可。 即使歸檔進程落在了後面一點,正常的操作也會繼續進行。 如果歸檔進程慢很多,就會增加災難發生的時候丟失的資料量。 同時也意味著 pg_xlog/ 目錄包含大量未歸檔的日誌段文件, 並且可能最後超出了磁盤空間。我們建議您監控歸檔進程,確保它是按照您的意識運轉的。

如果您關心能夠恢復到目前即時的狀態,您可能需要採取幾個額外的步驟以確保目前的, 部分填充的 WAL 段也拷貝到了某些地方。這條對於生成很少 WAL 流量的伺服器 (或者在執行中有鬆弛階段的)特別重要,因為在一個 WAL 段文件完全填充滿進而可以歸檔之前, 可能需要很長時間。一個處理這些的可能的方法是設置一個 cron 作業, 週期性(比如每分鐘一次)地標識目前 WAL 段文件然後把它們保存到某個安全的地方。 歸檔的 WAL 段和保存的目前段就足夠保證您可以總是恢復到目前時間的一分鐘之內。 這個行為目前還不是內置於 PostgreSQL 的,因為我們不想把 archive_command 的定義複雜化,因為那樣就要要求它跟蹤成功歸檔但是卻又有不同時刻含義的同一個 WAL 文件。 archive_command 只是用於處理那些不再改變的 WAL 段文件; 除了錯誤重試之外,對於任何給出的文件名他都只被調用一次。

在寫自己的歸檔命令的時候,您應該假設被歸檔的文件最多 64 個字元長並且可以包含 ASCII 字母,數字,以及點的任意組合。 我們不必要記住原始的全路徑(%p),但是有必要記住文件名(%f)。

請注意儘管 WAL 歸檔允許您回復任何對您的 PostgreSQL 資料庫的資料做的修改, 在最初的基礎備份之後,它還是不會回復對配置文件的修改(也就是說,postgresql.confpg_hba.confpg_ident.conf),因為這些文件都是手工編輯的,而不是透過 SQL 操作來編輯的。 所以您可能會需要把您的配置文件放在一個日常文件系統備份過程即可處理到的地方。 參閱 Section 16.4.1 獲取如何重定位配置文件的知識。

22.3.2. 進行一次基礎備份

進行基礎備份的過程相當簡單:

  1. 確保 WAL 歸檔打開並且可以運轉。

  2. 以資料庫超級用戶身份連接到資料庫,發出命令

    SELECT pg_start_backup('label');

    這裡的 label 是任意您想使用的這次備份操作的唯一標識。 (一個好習慣是使用您想把備份轉儲文件放置的目的地的全路徑。) pg_start_backup 用您的備份的訊息,在您的集群目錄裡,建立一個備份標籤文件, 叫做 backup_label

    至於您連接到集群中的那個資料庫沒什麼關係。您可以忽略函數返回的結果; 但是如果它報告錯誤,那麼在繼續之前處理它。

  3. 執行備份,使用任何方便的文件系統工具,比如 tar 或者 cpio。 這些操作過程中既不需要關閉資料庫,也不希望關閉資料庫的操作。

  4. 再次以資料庫超級用戶身份連接資料庫,然後發出命令

    SELECT pg_stop_backup();

    如果這個返回成功,您的工作就完成了。

我們不需要太關心在 pg_start_backup 和開始實際的備份之間開銷的時間, 也不需要太關心備份結束和 pg_stop_backup 之間的時間; 幾分鐘的延遲不會搞砸事情。不過,您必須確保這些操作是按順序執行的而不是重疊執行的。

要保證您的備份轉儲包括所有資料庫集群目錄裡的文件(比如,/usr/local/pgsql/data)。 如果您在使用並未放置在這個目錄裡的資料表空間,也要小心地包含它們 (並且要確保您的備份轉儲歸檔符號連接是符號連接,否則,恢復會把您的資料表空間搞亂)。

不過,您可以在備份轉儲文件裡省略集群目錄裡的 pg_xlog/ 子目錄。 這個略微複雜些的動作是值得的,因為它減少了恢復的時候的錯誤。 如果 pg_xlog/ 是一個指向集群目錄之外的一個符號連接,那麼這件事情很容易處理, 出於性能考慮的時候經常這麼做。

要使用這個備份,您需要保存所有備份開始以及之後的 WAL 段文件。 為了幫助您實現這個任務,pg_stop_backup 函數建立一個備份歷史文件, 它馬上儲存到 WAL 歸檔區域。這個文件的名字是以您在使用備份的時候需要的第一個 WAL 段文件的名字命名的。 比如,如果開始 WAL 文件是 0000000100001234000055CD,那麼備份歷史文件將命名為類似 0000000100001234000055CD.007C9330.backup 這樣的東西。 (這個文件名的第二部分資料表示在該 WAL 文件裡面的準確位置,通常可以被忽略。) 一旦您安全地把備份轉儲文件歸了檔,那麼您就可以刪除所有那些數值名字在這個文件前面的歸檔的 WAL 段。 備份歷史文件只是一個小的文本文件。它包含您給予 pg_start_backup 的標籤字串, 以及備份的起始時間和終止時間。如果您使用這個標籤來資料表示轉儲文件放在哪裡, 如果需要的話,那麼歸檔的歷史文件就足夠告訴您轉儲文件存放在哪裡了。

因為您必須保留直到您最後一次基礎備份的所有歸檔的 WAL 文件, 那麼兩次基礎備份之間的間隔通常是根據您想在歸檔 WAL 文件上花多少儲存空間來定的。 您還應該考慮您準備在恢復上花多少時間,如果需要恢復的話 — 系統將需要重放所有那些段, 而如果最後一次基礎備份以來,時間已經很長了,那麼那些動作可能會花掉好些時間。

還有一件事值得一提,那就是 pg_start_backup 函數在資料庫集群目錄裡建立了一個叫 backup_label 的文件,它被 pg_stop_backup 刪除。 這個文件當然也會作為您的備份轉儲文件的一部分歸檔。這個備份標籤文件包含您給予 pg_start_backup 的標籤字串, 以及 pg_start_backup 執行的時刻,以及起始 WAL 文件的名字。 如果有混淆,那麼我們可以看看備份轉儲文件裡面然後判斷轉儲文件來自那個備份會話。

我們還可以在 postmaster 停止的時候製作一個備份轉儲。 在這種條件下,很明顯您不能使用 pg_start_backup 或者 pg_stop_backup, 並且因此您必須靠自己的手段來跟蹤備份轉儲文件都是那些,以及相關的 WAL 文件最遠走到哪裡。 通常使用上面的在線備份步驟更好些。

22.3.3. 從在線備份中恢復

好,最糟糕的事情發生了,現在您需要從備份中恢復。下面是步驟:

  1. 停止 postmaster,如果它還在執行的話。

  2. 如果您還有足夠的空間,把整個集群資料目錄和所有資料表空間拷貝到一個臨時位置, 以防萬一您之後還需要它們。請注意這個預防措施要求您在系統裡又足夠的剩餘空間來現有庫的保持兩份拷貝。 如果您沒有足夠的空間,那麼您至少需要把集群資料目錄的 pg_xlog 子目錄的內容拷貝到安全的地方, 因為它們可能包含系統宕掉的時候還沒有歸檔的日誌。

  3. 然後清理掉所有在該集群資料目錄裡的現存文件, 以及所有您使用的資料表空間裡根目錄下的現存文件。

  4. 從您的備份轉儲中恢復資料庫文件。要小心用正確的所有者(資料庫系統用戶,而不是 root!)和權限恢復它們。 如果您使用了資料表空間,您可能需要核實在 pg_tblspc/ 裡的符號連接都得到正確恢復。

  5. 刪除任何目前還在 pg_xlog/ 裡的文件;這些文件來自備份轉儲,因此它們可能比目前的老。 如果您就根本沒有歸檔 pg_xlog/,那麼重建之,要注意也要重建子目錄 pg_xlog/archive_status/

  6. 如果您有在步驟 2 裡面保存的 WAL 段文件,那麼把它們拷貝到 pg_xlog/。 (最好是拷貝它們,而不是把它們移動回來,這樣即使發生了糟糕的事情,您需要重啟的時候, 您也依然擁有未修改的文件。)

  7. 在集群資料目錄裡建立一個恢復命令文件 recovery.conf(參閱 Recovery Settings)。 您可能還需要臨時修改 pg_hba.conf 以避免普通用戶連接,直到您確信恢復已經正常了為止。

  8. 啟動 postmaster。postmaster 將進入恢復模式並且繼續讀取它需要的歸檔的 WAL 文件。 在恢復過程完成後,postmaster 將把 recovery.conf 改名為 recovery.done (以避免不小心因後面的崩潰再次進入恢復模式)然後開始正常的資料庫操作。

  9. 檢查資料庫的內容以確保您已經恢復到您期望的位置。 如果還沒有,回到步驟 1。如果全部正常,則恢復 pg_hba.conf 成正常狀態,允許您的用戶登錄。

所有這些操作的關鍵部分時設置一個恢復命令文件, 這個文件描述您希望如何恢復以及恢復應該走到哪裡。 您可以使用 recovery.conf.sample(通常安裝在安裝目錄的 share/ 子目錄裡)作為原型。 您必須在 recovery.conf 裡面聲明的一個東西是 restore_command, 它告訴系統如何拿回歸檔的 WAL 文件段。類似 archive_command, 這個是一個腳本命令字串。它可以包含 %f,這個變量會被需要的日誌文件名替換, 以及 %p,它會被要拷貝去的日誌文件的絕對路徑代替。 如果需要在命令裡替換真正的 %,寫 %%。 最簡單的有用命令是類似下面的東西

restore_command = 'cp /mnt/server/archivedir/%f %p'

這個命令將把以前歸檔的 WAL 段從目錄 /mnt/server/archivedir 拷貝過來。 您當然可以使用某些更複雜的東西,甚至是一個要求操作者裝配合適的磁帶的 shell 腳本。

重要的一點是:該命令在失敗的時候返回非零值。如果日誌文件沒有出現在規檔中,那麼該系統詢問該命令; 在問到的時候,它必須返回非零。這個不是錯誤條件。還要注意 %p 路徑的基礎名將和 %f 不一樣; 不要認為它們是可以互換的。

在歸檔中找不到的 WAL 段將被認為在 pg_xlog/ 裡;這樣就允許使用最近沒有歸檔的段。 但是在歸檔中的段將比 pg_xlog/ 中的優先。在檢索歸檔的文件的時候,系統將不會覆蓋現有的 pg_xlog/ 內容。

通常,恢復將處理所有可用的 WAL 段,因此把資料庫恢復到目前時間(或者是在所給出的可用 WAL 段數目的情況下, 我們能走到的最近的地方)。但是如果您想恢復到某些以前的時刻點(比如,就在菜鳥 DBA 刪除您的主要交易資料表之前), 那麼只需要在 recovery.conf 裡聲明要求的停止點。您可以透過日期/時間來聲明, 也可以透過特定交易 ID 的結束來聲明這個停止點,我們叫做"恢復目標"。 在我們寫到這些的時候,只有日期/時間選項比較有用, 因為我們沒有工具來幫助您精確地標識應該使用哪個交易 ID。

注意: 請注意停止點必須在備份的終止時間之後(也就是,pg_stop_backup 的時間)。 您無法使用一個基礎備份恢復到備份正在進行中的某個時刻。 (要想恢復到該時刻,您必須回到您以前的基礎備份,然後從那個位置向前滾動。)

22.3.3.1. 恢復設置

這些設置只能在 recovery.conf 裡面使用,並且只是在恢復的過程中起作用。 在任何之後的恢復中,您必須重新設置他們。恢復過程開始後,它們的值無法改變。

restore_command (string)

執行檢索歸檔 WAL 文件段序列的 shell 命令。這個參數是必須的。 字串中的任何 %f 都被從歸檔中檢索出來的文件名替換, 而任何 %p 都被替換為拷貝過去的伺服器上的絕對路徑。 需要在命令裡嵌入真正的 % 字元時,寫 %%

有一點很重要,那就是這個命令只有在成功的時候才返回零。 系統向這條命令詢問沒有在歸檔裡出現的文件名; 在這種情況下,它必須返回非零。比如:

restore_command = 'cp /mnt/server/archivedir/%f "%p"'
restore_command = 'copy /mnt/server/archivedir/%f "%p"'  # Windows

recovery_target_time (timestamp)

這個參數聲明恢復執行到達的時間戳。最多可以聲明一個 recovery_target_timerecovery_target_xid。預設是恢復到 WAL 日誌的結尾。 精確的停止點也受 recovery_target_inclusive 影響。

recovery_target_xid (string)

這個參數聲明恢復將到達的交易 ID。要注意的是,儘管交易 ID 在交易開始的時候是認為順序的, 但是交易可以以不同的數值順序完成。將要恢復的交易是那些在聲明的這個交易之前(可以選擇包括它提交的時候的)提交的。 最多可以聲明一個 recovery_target_xidrecovery_target_time。 預設是恢復到 WAL 日誌的結尾。精確的停止點也受 recovery_target_inclusive 影響。

recovery_target_inclusive (boolean)

聲明我們是否在恢復目標之後(true),還是正好在其之前(false)停止。 適用於 recovery_target_timerecovery_target_xid, 不管聲明的是哪個。它分別資料表示具有準確的提交時間或者 ID 的那些(個)交易,是否將包含在恢復之中。 預設是 true

recovery_target_timeline (string)

聲明恢復到一個特定的時間線。預設是恢復到進行基礎備份時的當時的目前時間線上。 只是在複雜的重新恢復的情況下,您才需要設置這個參數,也就是在您需要恢復到一個本身是在即時恢復之後到達的狀態下, 才需要這麼做。 參閱 Section 22.3.4 進行討論。

22.3.4. 時間線

能夠把資料庫恢復到以前的某個時間點的能力導致了一些類似科幻小說裡的時間跟蹤和並行宇宙這樣的複雜情況。 在資料庫的最初的歷史裡,可能您在週二下午 5:15 刪除掉了一個非常關鍵的資料表。 然後有條不紊地拿出備份,恢復到週二晚上 5:14 即時備份。在這個資料庫宇宙的歷史裡, 您從來沒有刪除過那個資料表。但是假如您後來認識到這麼干並非絕好的主意,並且想回到最初的歷史中的稍後的點。 您沒法這麼幹,因為在資料庫執行的時候,它覆蓋了一些 WAL 段文件的序列,這些序列就是在您希望回去的區間裡的。 因此您的確需要區分在您從那些原始資料庫歷史生成的 WAL 中完成即時恢復之後生成的 WAL 序列。

為了處理這些問題,PostgreSQL 有個叫時間線的概念。 每次您即時恢復到一個比 WAL 序列的結尾要早的時刻,那麼就建立一個新的時間線, 以資料表示在該次恢復之後生成的 WAL 記錄。(不過,如果恢復動作一尺處理到 WAL 的結尾, 我們就不會開始一個新的時間線:我們只是擴展現有個那個。)時間線 ID 號是 WAL 段文件名的一部分, 因此新的時間線並不會覆蓋以前的時間線生成的 WAL 資料。實際上我們可以歸檔許多不同的時間線。 雖然這些看起來像沒用的特性,但它卻可能常常是救命稻草。考慮一下您並不很確信應該恢復到那個時刻的情況, 這個時候您不得不做好幾次試驗性即時恢復然後從中找到舊歷史中最好的分支。 如果沒有時間線,那麼這個過程可能很快就會導致無法管理的混亂。 有了時間線,您可以恢復到任意以前的狀態, 包括恢復到您後來放棄的時間線分支的狀態。

每當建立一個新的時間線的時候,PostgreSQL 都建立一個"時間線歷史"文件, 它顯示自己從那個時間線分出來,以及何時分出來的。這些歷史文件是在從包含多個時間線的規黨中進行恢復時, 允許系統選取正確的 WAL 段文件的必要文件。因此,它們像 WAL 段文件一樣歸檔到 WAL 歸檔裡。 歷史文件只是很小的文本文件(不想段文件很大),所以獨立地保存他們代價很小,也值得做。 如果您喜歡,您可以在歷史文件裡加入註釋,錄自己為什麼設置一個時間線以及如何設置的等訊息。 這樣的註釋會在您有厚厚一堆不同的時間線需要選擇和分析的時候特別有價值。

恢復的預設的行為時沿著與備份基礎備份的同一個時間線恢復。 如果您像恢復到某些子時間線(也就是,您想回到某些本身就是在開始恢復企圖之後發生的狀態), 您需要在 recovery.conf 裡聲明目標時間線 ID。您無法恢復到比基礎備份更早的時間線分支。

22.3.5. 注意

在我們寫到這些的時候,在線備份技術還有幾個局限。它們可能在將來的版本中修補:

還要注意,目前的 WAL 格式佔地非常大,因為它包含許多磁盤影像。 這麼做對於崩潰恢復用途是合適的,因為我們可能需要修補部分寫入的磁盤頁。 但是對 PITR 操作卻沒必要儲存如此多頁面。將來開發的一個方面就是透過刪除無用的頁拷貝來壓縮歸檔的 WAL 資料。