如何利用PostgreSQL的延遲複製實現災備

weixin_34253539發表於2019-02-18

GitLab網站的運營工作由GitLab基礎設施團隊負責,同時這也是GitLab目前最大的例項:擁有約300萬使用者和近700萬個專案,是網際網路上最大的單租戶開源SaaS站點之一。

PostgreSQL是GitLab網站基礎設施的關鍵組成部分,我們採用了各種策略來提升系統彈性,抵禦各種因資料丟失導致的災難性事故。當然,事故的發生本來就是小概率事件,但我們也做好了備份和複製機制,一旦發生事故,可以從這些場景中恢復。

我們通常會存在這樣一種誤解:認為複製是備份資料庫的一種手段。但是,在這篇文章中,我們將探討如何通過延遲複製在意外刪除資料後恢復資料:在GitLab網站上,使用者刪除了gitlab-ce專案的標籤,與標籤相關聯的合併請求和問題也會丟失。

有了延遲副本,我們能夠在90分鐘內恢復資料。我們將介紹這個過程,以及延遲複製如何幫我們實現這一目標。

使用PostgreSQL進行時間點恢復

PostgreSQL提供了一個內建功能,可以將資料庫狀態恢復到某個特定時間點,這個功能叫作時間點恢復(PITR),它所使用的機制與保持副本最新的機制是一樣的:以整個資料庫叢集的一致快照(一種備份)為基礎,再將變更序列應用資料庫狀態上,直到達到某個時間點。

為了將這個功能用於冷備份,我們會定期對資料庫進行基礎備份,並將其儲存在歸檔中(在GitLab,我們將歸檔保留Google Cloud Storage中)。此外,我們通過歸檔預寫日誌(WAL)來跟蹤資料庫狀態的變化。有了這些,我們就可以執行PITR,以便從災難中恢復:從災難發生之前的快照開始,應用WAL歸檔中的變更,直到達到災難性事件發生之前。

什麼是延遲複製?

延遲複製是指應用來自WAL的延遲變更。也就是說,一個在物理時間X提交的事務只能在時間X + d的副本上可見(d為延遲時間)。

PostgreSQL提供了兩種方法用來設定資料庫的物理副本:歸檔恢復和流式複製。歸檔恢復基本上與PITR一樣,不同點在於它是以持續的方式進行的:我們不斷從WAL歸檔中獲取變更,並以持續的方式將它們應用於副本狀態。而流式複製直接從上游資料庫主機獲取WAL流。我們更喜歡使用歸檔恢復,因為它更易於管理,並提供了能夠跟上生產叢集步伐的效能。

如何配置延遲歸檔恢復

歸檔恢復的配置主要是在recovery.conf中,例如:

standby_mode = 'on'restore_command = '/usr/bin/envdir /etc/wal-e.d/env /opt/wal-e/bin/wal-e wal-fetch -p 4 \u0026quot;%f\u0026quot; \u0026quot;%p\u0026quot;'recovery_min_apply_delay = '8h'recovery_target_timeline = 'latest'

這樣就配置了一個具有歸檔恢復功能的延遲副本。它使用wal-e(https://github.com/wal-e/wal-e)從歸檔中獲取WAL段(restore_command),並將變更的應用延遲8小時(recovery_min_apply_delay)。副本將遵循歸檔中存在的任何時間線轉換,例如,由叢集故障轉移(recovery_target_timeline)引起的時間線轉換。

可以使用recovery_min_apply_delay來配置流式複製。但是,有一些關於複製插槽、熱備用反饋以及其他需要注意的問題。在我們的例子中,我們通過從WAL歸檔複製而不是使用流式複製來避免這些問題。

需要注意的是,recovery_min_apply_delay是在PostgreSQL 9.3中引入的。但是,在以前的版本中,延遲副本通常使用恢復管理函式(pg_xlog_replay_pause()、pg_xlog_replay_resume())的組合或在延遲期間從歸檔中隱藏WAL段來實現。

PostgreSQL是如何實現的?

研究PostgreSQL如何實現延遲恢復是一件非常有趣的事情。那麼讓我們來看看下面的recoveryApplyDelay(XlogReaderState)。從WAL中讀取的每個記錄時都會呼叫這個方法。

static boolrecoveryApplyDelay(XLogReaderState *record){\tuint8\t\txact_info;\tTimestampTz xtime;\tlong\t\tsecs;\tint\t\t\tmicrosecs;\t/* nothing to do if no delay configured */\tif (recovery_min_apply_delay \u0026lt;= 0)\t\treturn false;\t/* no delay is applied on a database not yet consistent */\tif (!reachedConsistency)\t\treturn false;\t/*\t * Is it a COMMIT record?\t *\t * We deliberately choose not to delay aborts since they have no effect on\t * MVCC. We already allow replay of records that don't have a timestamp,\t * so there is already opportunity for issues caused by early conflicts on\t * standbys.\t */\tif (XLogRecGetRmid(record) != RM_XACT_ID)\t\treturn false;\txact_info = XLogRecGetInfo(record) \u0026amp; XLOG_XACT_OPMASK;\tif (xact_info != XLOG_XACT_COMMIT \u0026amp;\u0026amp;\t\txact_info != XLOG_XACT_COMMIT_PREPARED)\t\treturn false;\tif (!getRecordTimestamp(record, \u0026amp;xtime))\t\treturn false;\trecoveryDelayUntilTime =\t\tTimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay);\t/*\t * Exit without arming the latch if it's already past time to apply this\t * record\t */\tTimestampDifference(GetCurrentTimestamp(), recoveryDelayUntilTime,\t\t\t\t\t\t\u0026amp;secs, \u0026amp;microsecs);\tif (secs \u0026lt;= 0 \u0026amp;\u0026amp; microsecs \u0026lt;= 0)\t\treturn false;\twhile (true)\t{        // Shortened:        // Use WaitLatch until we reached recoveryDelayUntilTime        // and then        break;\t}\treturn true;}

這裡的重點是,延遲是基於與事務的提交時間戳(xtime)一起記錄的物理時間。我們還可以看到,延遲只被應用於提交記錄,不會被應用於其他型別的記錄:直接應用資料變更,但相應的提交會延遲,因此這些變更只在配置的延遲後才可見。

如何使用延遲副本來恢復資料

假設我們有一個生產資料庫叢集和一個具有8小時延遲的副本。我們如何使用它來恢復資料?讓我們來看看在意外刪除標籤的情況下如何進行恢復。

在事件發生之後,我們馬上在延遲副本上暫停歸檔恢復:

SELECT pg_xlog_replay_pause();

暫停副本可以避免副本重放DELETE查詢的危險。如果你需要更多時間進行診斷,這個操作就非常有用。

恢復方法是讓延遲副本趕在DELETE查詢發生之前。在我們的例子中,我們大致知道DELETE查詢的物理時間。我們從recovery.conf中刪除了recovery_min_apply_delay,並新增了recovery_target_time。這樣可以讓副本儘可能快地趕上(沒有延遲),直到達到某個時間點:

recovery_target_time = '2018-10-12 09:25:00+00'

在使用物理時間戳進行操作時,最好可以為錯誤留一點餘地。顯然,餘地越大,資料丟失就越多。如果副本恢復超出實際的事件時間戳,它也會重放DELETE查詢,我們將不得不重新開始(或者更糟:使用冷備份來執行PITR)。

重新啟動延遲的Postgres例項後,我們看到很多WAL段被重放,直到達到目標事務時間。為了瞭解這個階段的進度,我們可以使用這個查詢:

SELECT  -- current location in WAL  pg_last_xlog_replay_location(),  -- current transaction timestamp (state of the replica)  pg_last_xact_replay_timestamp(),  -- current physical time  now(),  -- the amount of time still to be applied until recovery_target_time has been reached  '2018-10-12 09:25:00+00'::timestamptz - pg_last_xact_replay_timestamp() as delay;

當重放時間戳不再發生變化時,我們就知道恢復已經完成了。我們可以考慮設定recovery_target_action,以便在重放完成後關閉、提升或暫停例項(預設為暫停)。

資料庫現在處於災難性查詢之前的狀態。我們可以開始匯出資料或以其他方式使用資料庫。在我們的示例中,我們匯出了有關已刪除標籤的資訊及其與問題和合並請求的關聯,並將資料匯入生產資料庫。在其他資料丟失更嚴重的情況下,可以將副本提升並繼續作為主要副本使用。然而,這意味著我們會丟失在恢復到某個時間點之後寫入資料庫的任何資料。

使用物理時間戳進行目標恢復的替代方法是使用事務ID。記錄事務ID是一種很好的做法,例如,使用log_statements ='ddl’可以記錄DDL語句(如DROP TABLE)。如果我們手頭有一個事務ID,可以使用recovery_target_xid來重放到DELETE查詢之前的事務。

對於延遲複本,恢復正常的方法很簡單:恢復recovery.conf的改動,並重新啟動Postgres。過了一會兒,副本將再次顯示8小時的延遲——為未來的災難做好準備。

歸檔恢復的好處

與使用冷備份相比,延遲副本的主要好處是它消除了從歸檔中恢復完整快照的步驟。這可能需要數小時時間,具體取決於網路和儲存速度。在我們的例子中,從歸檔中獲取完整的約2TB備份大約需要五個小時。除此之外,我們必須應用24小時的WAL才能恢復到理想的狀態(在最壞的情況下)。

相比冷備份,使用延遲副本的兩個好處是:

  1. 無需從歸檔中獲取完整的備份;
  2. 我們有一個8個小時的WAL固定視窗,需要重放才能趕上。

除此之外,我們還不斷測試我們從WAL歸檔執行PITR的能力,並通過監控延遲副本的滯後來快速實現WAL歸檔損壞或其他與WAL相關的問題。

在我們的示例中,完成恢復需要50分鐘時間,並轉換為每小時110GB WAL的恢復速率(當時歸檔仍在AWS S3上)。在工作開始90分鐘後,事故得到緩解,資料得到恢復。

總結:延遲複製什麼時候有用,什麼時候沒有用

延遲複製可以用作從意外資料丟失中恢復資料的第一手段,並且非常適用於在配置的延遲時間內可以知道引起丟失的事件的情況。

讓我們明確一點:複製不是一種備份機制。

備份和複製是兩種具有不同目的的機制:冷備份對於從災難中恢復來說很有用,例如意外的DELETE或DROP TABLE事件。在這種情況下,我們利用冷儲存中的備份來恢復表或整個資料庫的早期狀態。另一方面,DROP TABLE幾乎可以立即複製到正在執行的叢集中的所有副本——因此正常的複製對於從這個場景中恢復是沒有用的。相反,複製的目的主要是保護資料庫可用性,防止單個資料庫出現故障,以及用於分發負載。

即使存在延遲副本,在某些情況下我們確實冷備份,並把它儲存在安全的地方:資料中心故障、靜默損壞或其他不可見的事件,都需要依賴冷備份。如果只進行復制,我們可能沒有那麼多的好運。

英文原文:https://about.gitlab.com/2019/02/13/delayed-replication-for-disaster-recovery-with-postgresql/

相關文章