摘要: 標籤PostgreSQL , 物理備份, 時間點復原, PITR , 增量備份, 歸檔, 一致性, 邏輯檢查點, 時間軸幕後PostgreSQL支援PITR即時間點復原,為了支援時間點復原,至少需要一次全量備份,然後需要歸檔日誌。
幕後
PostgreSQL支援PITR即時間點復原,為了支援時間點復原,至少需要一次全量備份,然後需要歸檔日誌。
這句話標題可能不夠清晰,至少需要哪些歸檔日誌,全量備份的時間點有沒有要求呢?
本文要解答這個問題。
什麼是全量備份
全量備份指的是對資料庫的$PGDATA以及所有資料表空間檔案(包括全域資料檔案、交易記錄檔案、設定檔、控制檔案、資料表空間資料檔案等)進行一次全量的拷貝。
一個資料庫的目錄結構通常如下
cd $PGDATA
drwx------ 6 digoal digoal 4.0K Aug 13 08:23 base
-rw------- 1 digoal digoal44 Aug 23 00:00 current_logfiles
drwx------ 2 digoal digoal 4.0K Aug 16 11:28 global
drwx------ 2 digoal digoal 4.0K Aug 23 00:00 log
drwx------ 2 digoal digoal 4.0K Aug 13 07:25 pg_commit_ts
drwx------ 2 digoal digoal 4.0K Aug 13 07:25 pg_dynshmem
-rw------- 1 digoal digoal 4.5K Aug 13 07:25 pg_hba.conf
-rw------- 1 digoal digoal 1.6K Aug 13 07:25 pg_ident.conf
drwx------ 4 digoal digoal 4.0K Aug 23 10:18 pg_logical
drwx------ 4 digoal digoal 4.0K Aug 13 07:25 pg_multixact
drwx------ 2 digoal digoal 4.0K Aug 16 11:28 pg_notify
drwx------ 2 digoal digoal 4.0K Aug 13 07:25 pg_replslot
drwx------ 2 digoal digoal 4.0K Aug 13 07:25 pg_serial
drwx------ 2 digoal digoal 4.0K Aug 13 07:25 pg_snapshots
drwx------ 2 digoal digoal 4.0K Aug 16 11:28 pg_stat
drwx------ 2 digoal digoal 4.0K Aug 23 13:57 pg_stat_tmp
drwx------ 2 digoal digoal20K Aug 14 13:13 pg_subtrans
drwx------ 2 digoal digoal 4.0K Aug 13 07:25 pg_tblspc
drwx------ 2 digoal digoal 4.0K Aug 13 07:25 pg_twophase
-rw------- 1 digoal digoal3 Aug 13 07:25 PG_VERSION
drwx------ 3 digoal digoal 1.3M Aug 23 10:18 pg_wal
drwx------ 2 digoal digoal 4.0K Aug 14 22:00 pg_xact
-rw------- 1 digoal digoal 2.2K Aug 13 07:26 postgresql.auto.conf
-rw------- 1 digoal digoal23K Aug 16 11:25 postgresql.conf
-rw------- 1 digoal digoal34 Aug 16 11:28 postmaster.opts
-rw------- 1 digoal digoal90 Aug 16 11:28 postmaster.pid
注意pg_tblspc裡面是軟連結,這裡面對應的是資料表空間目錄。也需要備份。
全量備份可以通過COPY檔案的方式,給檔案系統、塊裝置打快照的方式,等進行全量的備份。
COPY檔案可以使用作業系統的指令(如果這麼做,建議你考慮到軟連結的問題,一定要記得備份實際的檔案)。
如果是遠端備份,可以設定資料庫的流複製,通過pg_basebackup指令進行流式的備份,這樣你不需要考慮資料表空間需要單獨備份的問題,pg_basebackup會幫你做掉。
不管何種方式備份(除了pg_basebackup),都需要你執行pg_start_backup(''),這樣資料庫會做一次檢查點,同時強制打開full page write(確保即使某些用戶關閉了full page write,備份還是有效。),拷貝完後,執行pg_stop_backup()。注意pg_stop_bacup()之前,你的備份是不正確。所以備份完成一定要記得pg_stop_backup()。後面會說為什麼要這麼做。這些步驟pg_basebackup會自動幫你做。
什麼是不一致拷貝
因為全量備份是不需要停庫,也不影響商務的。屬於熱備份。
熱備份比如會帶來一個問題,例如用戶在寫資料,資料庫在刷髒頁等動作,你在備份時可能拷貝走的檔案是partial block,一個塊中有一半新的一半舊版資料。造成不一致。
不過你不需要擔心這個不一致的問題,因為PG考慮到了,並且有方法解決它。
這也是為什麼需要執行pg_start_backup(),開啟fullpage write的原因。開啟full page write後,檢查點之後,任何一個BLOCK第一次變成dirty block時,都會往WAL裡面寫下完整的資料區塊。
通過wal的完整資料區塊,可以修復備份程序中拷貝走的不一致資料區塊。
什麼是一致性位點
既然備份走的檔案裡有不一致的資料區塊,以及PG有不一致的修復方法。那麼就一定有一致的位點。
什麼是一致的位點呢?就是指資料庫認為所有的資料區塊都是一致的,沒有partial write(一半新、一半舊)的情況。
什麼是檢查點,資料庫DOWN機、伺服器DOWN機如何復原到一致性狀態
檢查點是資料庫的一致性點,做檢查點的目的是將SHARED BUFFER中的髒頁刷到磁碟中持久化。同時開啟full page write的情況下,檢查點之後第一次變成dirty block時,都會往WAL裡面寫下完整的資料區塊。
做檢查點可能需要一定的時間,這段時間隨著資料庫的讀寫,會產生一些WAL,因此檢查點對應到WAL檔案中,有一個開始置放和結束置放,比如開始置放在WAL檔案A中,結束置放在WAL檔案F中。
當資料程式庫伺服器異常DOWN機時,是需要復原的。從資料庫最後一次完成的檢查點的WAL開始置放追蹤WAL開始復原,一直復原到檢查點的邏輯置放結束置放為止。
即要復原到一致位元點,需要從A到F(CKPT的結束RECORD),只有到了這裡,資料庫才是一致的狀態。
備份組如何復原到一致性位點
那麼備份組如何達到一致位點呢?
其實原理和檢查點差不多,通過全量備份組來復原,至少也要復原到pg_stop_backup()的置放。
例如早上9點開始全量備份,早上11點備份結束,備份期間(9到11點)產生了A-F這些WAL檔案。
那麼這個備份組必須要包含A-F這些檔案,才能復原到一致性的位點。
為什麼這麼說,你想像一下,假設你在10點59快要備份結束的時候,資料庫產生了一個髒頁,並write到磁碟,此時你剛好拷貝到了這個partial block。而這個BLOCK是最後一次檢查點之後第一次變更的BLOCK,那麼你的備份組如果要復原到一致性位點,必須使用F這個檔案內(包含了這個塊的FULL PAGE)來復原這個BLOCK到一致的狀態。
是不是很好理解呢?
什麼情況下資料庫會處於recovery狀態不起來
當資料庫在復原時,如果沒有達到一致性的狀態(即前面提到的,沒有復原到必要的WAL位點)時,資料庫會處於recovery狀態無法串連。即使使用了standby圖樣,也一樣。
採取什麼措施
1. 繼續追蹤其他的WAL,並復原WAL,直到復原到一致性點的WAL。(例如檢查點邏輯置放,或者全量備份pg_stop_backup()的置放)。
2. 如果你沒有足夠的wal可用來復原(無法達到一致性位點),怎麼辦呢?你可以強制promote啟用。但是有可能遇到塊錯誤的風險。當讀取到這類資料區塊時會報錯,可以使用zero demage block隱藏參數來跳過它。或者使資料庫將這些塊設定為INIT狀態(使用vacuum freeze可以處理它)。
為什麼檢查點不適合跨度太大
postgresql有參數控制檢查點的跨度,檢查點做得太快(跨度小),檢查點做得慢(跨度大)。
當資料庫很繁忙時,一個檢查點可能會跨越若干個WAL檔案,為了到達一致性的位元點,至少需要APPLY這些跨越的WAL檔案才行。
最好的辦法:
資料庫產生髒頁多,並且很繁忙時,跨度大一點,避免檢查點引入的FSYNC IO開銷影響商務。
資料庫產生的髒頁小,不繁忙時,跨度小一點,讓資料庫快速的到達一致性狀態。
好在PostgreSQL 9.6開始就支援根據負載動態調整檢查點的跨度了。
PITR(時間點復原)一致性的要素總結
1、資料庫有兩個一致性的點,檢查點結束位點、全量備份的pg_stop_backup()位點。
為了讓資料庫可以復原到一致性位元點,需要足夠的WAL,復原到這兩個位點以上,資料庫才是一致的狀態。
開啟full page write,並且做好WAL歸檔是非常重要的,確保你在任意時候都可以復原到一致的狀態。
2、不要讓檢查點的時間太長,這樣可以讓資料庫快速達到一致性狀態。(PG 9.6已經支援動態CKPT調度,很棒吧)
3、recovery.conf中有一個參數,復原到一致性的點即停止並pause或promote,是一個很不錯的developor參數。
什麼是時間軸、復原時如何利用時間軸檔案
時間軸是資料庫promote的時候產生的,當PostgreSQL的standby節點從唯讀節點變成讀寫節點時,會自動建立一個時間軸檔案。時間軸檔案是用於標記資料庫是什麼時候啟用的。
時間軸檔案中包含了新時間軸的第一條WAL記錄的置放,也即是上一個時間軸的最後一筆WAL RECORD的結束置放。
cat 00000002.history
1660/95B6F2A0no recovery target specified ---意思是復原到這裡,你就不要再恢復線1的WAL了。請切到時間軸2。
剖析時間軸1切換時的WAL檔案
pg_waldump000000010000066000000095|less
rmgr: Heaplen (rec/tot):911/911, tx:474860760, lsn: 660/95B66B80, prev 660/95B66B58, desc: HOT_UPDATE off 12 xmax 474860760 ; new off 14 xmax 0, blkref #0: rel 1663/13146/2619 blk 9655
rmgr: Heaplen (rec/tot):54/54, tx:474860760, lsn: 660/95B66F10, prev 660/95B66B80, desc: LOCK off 13: xid 474860760: flags 0 LOCK_ONLY EXCL_LOCK , blkref #0: rel 1663/13146/2619 blk 9655
rmgr: Heaplen (rec/tot):73/ 31477, tx:474860760, lsn: 660/95B66F48, prev 660/95B66F10, desc: UPDATE off 13 xmax 474860760 ; new off 13 xmax 0, blkref #0: rel 1663/13146/2619 blk 9658 FPW, blkref #1: rel 1663/13146/2619 blk 9655
rmgr: Btreelen (rec/tot):64/64, tx:474860760, lsn: 660/95B6EAA0, prev 660/95B66F48, desc: INSERT_LEAF off 1384, blkref #0: rel 1663/13146/2696 blk 126
rmgr: Heaplen (rec/tot):1251/1251, tx:474860760, lsn: 660/95B6EAE0, prev 660/95B6EAA0, desc: HOT_UPDATE off 10 xmax 474860760 ; new off 11 xmax 0, blkref #0: rel 1663/13146/2619 blk 9657
rmgr: Heaplen (rec/tot):184/184, tx:474860760, lsn: 660/95B6EFC8, prev 660/95B6EAE0, desc: INPLACE off 14, blkref #0: rel 1663/13146/1259 blk 0
rmgr: Transaction len (rec/tot):34/34, tx:474860760, lsn: 660/95B6F080, prev 660/95B6EFC8, desc: COMMIT 2017-08-23 09:46:13.592320 CST
rmgr: XLOGlen (rec/tot):106/106, tx:0, lsn: 660/95B6F0A8, prev 660/95B6F080, desc: CHECKPOINT_ONLINE redo 660/95B6F0A8; tli 1; prev tli 1; fpw true; xid 0:474860761; oid 683262; multi 1; offset 0; oldest xid 274864396 in DB 13146; oldest multi 1 in DB 13146; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; online
rmgr: XLOGlen (rec/tot):106/106, tx:0, lsn: 660/95B6F118, prev 660/95B6F0A8, desc: CHECKPOINT_SHUTDOWN redo 660/95B6F118; tli 1; prev tli 1; fpw true; xid 0:474860761; oid 675073; multi 1; offset 0; oldest xid 274864396 in DB 13146; oldest multi 1 in DB 13146; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown
rmgr: XLOGlen (rec/tot):106/106, tx:0, lsn: 660/95B6F188, prev 660/95B6F118, desc: CHECKPOINT_SHUTDOWN redo 660/95B6F188; tli 1; prev tli 1; fpw true; xid 0:474860761; oid 675073; multi 1; offset 0; oldest xid 274864396 in DB 13146; oldest multi 1 in DB 13146; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown
rmgr: XLOGlen (rec/tot):50/50, tx:0, lsn: 660/95B6F1F8, prev 660/95B6F188, desc: PARAMETER_CHANGE max_connections=1000 max_worker_processes=128 max_prepared_xacts=0 max_locks_per_xact=6400 wal_level=replica wal_log_hints=off track_commit_timestamp=off
rmgr: XLOGlen (rec/tot):106/106, tx:0, lsn: 660/95B6F230, prev 660/95B6F1F8, desc: CHECKPOINT_SHUTDOWN redo 660/95B6F230; tli 1; prev tli 1; fpw true; xid 0:474860761; oid 675073; multi 1; offset 0; oldest xid 274864396 in DB 13146; oldest multi 1 in DB 13146; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown
-----這是上一個時間軸檔案的內容,最後一條即時間軸檔案中的前一條RECORD。
剖析時間軸2的第一個WAL檔案。
pg_waldump000000020000066000000095|less
rmgr: XLOGlen (rec/tot):50/50, tx:0, lsn: 660/95B6F1F8, prev 660/95B6F188, desc: PARAMETER_CHANGE max_connections=1000 max_worker_processes=128 max_prepared_xacts=0 max_locks_per_xact=6400 wal_level=replica wal
_log_hints=off track_commit_timestamp=off
rmgr: XLOGlen (rec/tot):106/106, tx:0, lsn: 660/95B6F230, prev 660/95B6F1F8, desc: CHECKPOINT_SHUTDOWN redo 660/95B6F230; tli 1; prev tli 1; fpw true; xid 0:474860761; oid 675073; multi 1; offset 0; oldest xid 27
4864396 in DB 13146; oldest multi 1 in DB 13146; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown
-----這條之前的WAL都是繼承自上一個時間軸的,000000020000066000000095就是從000000010000066000000095複製出來的檔案。
rmgr: XLOGlen (rec/tot):42/42, tx:0, lsn: 660/95B6F2A0, prev 660/95B6F230, desc: END_OF_RECOVERY tli 2; prev tli 1; time 2017-08-23 16:48:43.384886 CST
rmgr: Standbylen (rec/tot):50/50, tx:0, lsn: 660/95B6F2D0, prev 660/95B6F2A0, desc: RUNNING_XACTS nextXid 474860761 latestCompletedXid 474860760 oldestRunningXid 474860761
rmgr: Standbylen (rec/tot):50/50, tx:0, lsn: 660/95B6F308, prev 660/95B6F2D0, desc: RUNNING_XACTS nextXid 474860761 latestCompletedXid 474860760 oldestRunningXid 474860761
rmgr: XLOGlen (rec/tot):106/106, tx:0, lsn: 660/95B6F340, prev 660/95B6F308, desc: CHECKPOINT_ONLINE redo 660/95B6F2D0; tli 2; prev tli 2; fpw true; xid 0:474860761; oid 675073; multi 1; offset 0; oldest xid 274864396 in DB 13146; oldest multi 1 in DB 13146; oldest/newest commit timestamp xid: 0/0; oldest running xid 474860761; online
rmgr: Standbylen (rec/tot):50/50, tx:0, lsn: 660/95B6F3B0, prev 660/95B6F340, desc: RUNNING_XACTS nextXid 474860761 latestCompletedXid 474860760 oldestRunningXid 474860761
recovery.conf檔案中包含了對時間軸的這樣一段標題。
當用戶復原資料庫時,如果要復原到本期控制檔案更大的時間軸,(即跨時間軸復原,復原到另一個啟用的資料庫),那麼需要設定為latest。
# If you want to recover into a timeline other than the "main line" shown in
# pg_control, specify the timeline number here, or write 'latest' to get
# the latest branch for which there's a history file.
#
#recovery_target_timeline = 'latest'
在瞭解了時間軸的原理後,我們就需要注意一件事情。
如果你的系統中有非同步流複製關聯的主備,並且主備都有歸檔檔案時,千萬不要搞錯了他們的關聯。
例如
1、A是主庫
2、B是非同步備庫
3、A產生了一堆WAL歸檔。
4、某一時刻t1,做了一個全量備份
5、之後的某一時刻t2,HA程式認為A掛了,把B啟用成為新的主庫,啟用時產生了幾個檔案:上一個時間軸的最後一個WAL,新時間軸的第一個WAL,以及一個新時間軸檔案,告訴你切換的WAL位點是,1 660/95B6F2A0 no recoverytarget specified,然後
6、由於是非同步模式,A還有一些WAL沒有發給B。
7、用戶想復原到t3的某一時刻,
8、如果你需要用老的備份組復原到t3,那麼就涉及到跨時間軸復原。注意當復原需要用到B啟用時的臨界WAL檔案時,千萬不要使用A歸檔的臨界WAL檔案來復原,否則會在老的時間軸越走越遠。
9、為了讓復原走上新時間軸的道路,需要具備B上面產生的三個檔案:上一個時間軸的最後一個WAL,新時間軸的第一個WAL,以及一個新時間軸檔案。
建議:
在切換時間軸後,使用新的主庫做一次全量備份。
資料庫備份、復原、容災最佳實踐文件
《PostgreSQLon ECS多雲盤的部署、快照備份和復原》
《PostgreSQL最佳實踐 - 塊級增量備份(ZFS篇)驗證 -recovery test script for zfs snapshot clone + postgresql stream replication +archive》
《PostgreSQL最佳實踐 - 塊級增量備份(ZFS篇)雙機HA與區塊層級備份部署》
《PostgreSQL最佳實踐 - 塊級增量備份(ZFS篇)單個資料庫採用多個zfs卷(如資料表空間)時如何一致性備份》
《PostgreSQL最佳實踐 - 塊級增量備份(ZFS篇)備份組自動校驗》
《PostgreSQL最佳實踐 - 塊級增量備份(ZFS篇)方案與實戰》
《PostgreSQL最佳實踐 - 塊等級增量備份(pg_rman baseon LSN)源碼淺析與使用》
《PostgreSQL最佳實踐 - 任意時間點復原源碼剖析》
《PostgreSQL最佳實踐 - 線上增量備份與任意時間點復原》
相關產品:
1. 雲資料庫MongoDB 版
2. 巨量資料計算服務(MaxCompute)
3. 歸檔隱藏
4. 雲端服務器ECS