Postgres - 設定 physical replication 教學

上次提到 pg_upgrade 可以快速 upgrade 單台的 Postgres Server,最後結尾有提到要如何針對 PG Cluster 做升級。這篇先不講,先來記錄一下如何快速在 PG 建立起 cluster 並且採用 physical replication。

這樣下篇文章才可以來講講如何快速針對 PG Cluster 做升級。

因此這邊文章將會記錄一下如何做出 Physical Replication,以及 9.6 ~ 11 基本上設定一樣,但是在 12 後,設定有點不同,以下都會介紹到。

何謂 Physical Replication

前幾次的文章有介紹什麼是 logical replication,這個其實是 11 之後推出的功能,那最一開始建立 PG Cluster 的方式就是透過 Physical Replication (但又稱為 Stream Replication,我覺得叫 Physical Replication 比較名符其實。),講白的就是建立了一個 Primary 可讀可寫,在建立起多個 Replica 只可以讀,來應付大量的讀取需求下的 Cluster 模式。

而 logical replication 我有提到說,不支援 DDL 操作,還有等等其他的限制,相對的 Physical Replication 是會傳 WAL 過去,所以任何的操作都可以支援。

首先可以想像 Primary 有 WAL Sender,Replica 則是 WAL Receiver,要啟動 Physical Replication 會需要以下的步驟:

  1. Replica 需要先對 Primary 發起 IDENTIFY_SYSTEM 請求,用來確認 Primary 的身份,這個問題涉及兩個層面要理解

    1. Replica 怎麼知道 Primary 位置在哪裡?在 Postgres 11 (含) 之前要設定 primary_conninfo 在 recovery.conf,在 Postgres 12 (含) 之後要設定 primary_conninfo 在 postgresql.conf,如果你架設 Replica Server 是透過跑 pg_basebackup 的話那 Postgres 會自動產生 postgres.auto.conf 在你的 pgdata 上,裡面就會自動填上 primary_conninfo 了。

      還有就是 Postgres 12 之後會如果有跑 pg_basebackup 也會自動的建立 standby.signal 在 pgdata 上代表這個 Replica Server 是 hot standy。

    2. 好啦,Primary 位置知道了,但是要怎麼確認這個 Primary 就是我的真的 Primary 呢?利用的是 IDENTIFY_SYSTEM,這個怎麼來的,是透過 Postgres initdb 的指令來的,每一次資料要初始化之後都會分配一個唯一的 database system identifier,Primary 與 Replica 的 database system identifier 必須要相同的,他們才能認識對方,也才能組成一個 Cluster。

      這就是為什麼在跑 pg_basebackup 之前也先將 Replica Server 之前的 pg data 先全部砍掉,再跑,其中的原因就是要確跑 database system identifier 必須跟 Primary Server 一樣。

  2. 當 Replica 與 Primary 互相確認完身份後,這時候 Replica 就會發送 START_REPLICATION 請求,內容像是 Replica Server 想要獲取的 WAL ,Primary 會看是否存在,如果不存在,就會給出錯誤訊息,Replica Server 收到就會退出,然後 Physical Replication 就會失敗

    這種失敗最會出現在什麼時候?

    1. Replica Server shut down 此時落後 Primary Server,當 Replica Server 重啟後就會無法連接上 Primary,因為 Replica 無法追趕這之間的資料落差。
    2. Primary Server 有大量的寫入,Replica 還來不及與 Primary Server 同步完資料,Primary 就把 WAL 刪掉了,那也會出現這種失敗,因為同樣的 Replica 無法追趕這之間的資料落差

    有沒有辦法可以解決?當然可以,後面我會提到。

各種名詞介紹

在設定 Physical Replication 的參數時,那時候我看官網或是網路上的教學文章,會覺得名詞很多又複雜,而且 9.6 ~ 11 與 12 ~ 15 之間的設定又有做了調整,整個就覺得到底是殺小的感覺。

所以我也來記錄一下各種名詞解釋,讓未來的自己不再迷惘。

cold / warm / hot standby

在設定的時候最常看見的就是什麼是 cold/warm/hot standby?首先 standby 的意思就是 primary 的分身,但 primary 掛掉後,standby 就要擔起取代 primary 的重責大任,所以 standby 與 primary 不應該要有資料落差,通常會需要 real time 的保持最新資料。那前面的形容詞 cold 跟 warm 跟 hot 是什麼?cold 就是一台 Offline Replica Server,等 Primary 掛掉後,要透過 failover 機制將這台 Cold Replica Server 啟動並且變成 Primary。那 Warm 跟 Hot 就是 Online Replica Server,唯一差別在於 Warm 不提供 Client 連線,但是 Hot 可以提供 Client 端連線但只能應用讀取的操作,所以好處在哪?Hot Standby 可以實現讀取操作的 load balance 的效果,這也是 Postgres Cluster 的最大意義,所以通常架設 Postgres Cluster 都是為了想要有 Hot Standby 的機制才會架設的,其他其實現實上的用處並不大。

WAL Files

WAL 的概念就是每一個 Postgres commit 的 transaction 紀錄都會先記錄 WAL file 裏面,這樣才能確保未來當 Server 掛掉,Postgres 可以拿 WAL 來 recovery 回來。也因此 Primary Server 也會將 WAL file 傳過去給 Replica server,讓 Replica Server 可以透過 WAL file 來跟上 Primary 的資料進度,這些 WAL file 被放在 pg_wal,如果在 Postgres 10 版本之前叫做 pg_xlog。

過時的 WAL 會被清理掉,也就是說前面我們提到如果 Primary Server 寫入量過大,Replica 還來不及收到 WAL,舊的 WAL 就被 Primary 清掉的話,就會導致 Replica Server 無法繼續跟上 Primary Server 的資料進度。

後面會提該怎麼處理這件事情。

Recovery Mode

什麼是 Recovery Mode?通常如果你的 Postgres 的 pgdata 資料夾下如果有放 recovery.conf,就會被 Postgres 視為這台 Server 是 recovery mode。請注意 recover.conf 只有 Postgres 11 (含) 之前才會有,Postgres 12 (含) 之後捨棄了 recovery.conf 的設定。

那 Postgres 12 之後怎樣才會進入 Recovery Mode,透過的是在 pgdata 下建立 recovery.signal 的空檔案,就會被 Postgres 視為是進入 Recovery Mode。

那如果 Postgres 進入了 Recovery Mode 的話,會透過所謂的 archive_command 與 restore_command 來互相搭配實現 recovery,簡單來說今天如果你的 Postgres shutdown 了,要知道 Postgres 可能還來不及將 WAL 裡面所對應的資料真正意義的儲存到 disk 此時 Postgres 就 shutdown,那這之間的資料落差該怎麼處理?當然是透過 Postgres 的 Recovery 的機制,因為 Postgres 可以透過 archive_command 將你的 WAL 儲存到第三方空間,注意 archive_command 接的是 command 意味者你可以給任何 bash 指令在上面達到你想要的效果。

你可能會覺得 WAL 都會存放在 pg_wal 或是叫做 pg_xlog 那為什麼還需要用 archive_command 將你的 WAL 儲存到其他空間,首先 pg_wal 會將過時 wal 刪掉,所以當你想要達到 Point-in-Time Recovery 你一定會需要哪個時段的 WAL。另外就是備份的效果,如果你有定時備份你的 pgdata 與 pg_wal 的資料,那就能順利做到 Point-in-Time Recovery。

詳請請參考:https://www.postgresql.org/docs/current/continuous-archiving.html

archive_command 跟 restore_command 可以這樣操作:

1
2
archive_command = 's3cmd put %p s3://BUCKET/path/%f' # in postgresql.conf
restore_command = 's3cmd get s3://BUCKET/path/%f %p' # in recovery.conf

所以 archive_command 跟 restore_command 是要有權限可以對同樣儲存位置做存取才可以。

所以有 Recovery Mode 可以解決 Replica Server 重啟後想追上 Primary Server 的資料進度的其中一個解法。

因此當你有設定 archive_command 後,如果成功運行 archive_command,Postgres 就會主動將過時的 WAL file 刪掉了。

Physical Replication Slot

前面提到有 Recovery Mode 可以解決資料落差的問題,另外一個解法就是 Physical Replication Slot。那有 Physical Replication Slot 就有 Logical Replication Slot 這個有機會我們再說,但反正 Physical Replication Slot 性質跟 Archive 其實是類似的,

Physical Replication Slot 也是會找地方保留 WAL file,但是 Primary Server 會去看 Slot 裡面的 WAL file 是否都已經被 Replica Server 所拿到了,才會認定是過時的,才會去刪除 WAL file,因此:

  • 確保 primary 保留足夠的 WAL 供所有副本接收它們
  • 防止 primary 刪除可能導致 replica 恢復衝突的行
  • primary 只有在事務日誌被 all replica 使用後才能回收

缺點是什麼呢?就是 primary 的 WAL file 可能會一直累積,假設你的 Replica 一直無法追上,那造成 disk 用量過多。

要怎麼建立 Physical Replication Slot 呢:

1
2
3
4
5
# 在 primary server 的 postgresql.conf 設定
max_replication_slots = 1

# 在 replica server 的 recovery.conf 設定
primary_slot_name = 'myslot1'

代表設定一個 Server 最多的 replication_slots

請注意在 PG < 10 之前都是這樣設定的,但是在 PG 10 之後預設 max_replication_slots = 10。所以預設是開啟的。

在 replica server 設定的 primary_slot_name 的名稱是要在 primary 去 create 的:

1
SELECT pg_create_physical_replication_slot('myslot1');

這樣就算是建立完成了。

之後 Replica Server 就會根據這個 slot 去拿 wal file 也不用當心重啟後追趕不上 primary server 了。

如果想要監控 physical slot 可以利用這個指令:

1
SELECT * FROM pg_replication_slots;

如果要關掉 physical slot 的話要先在 replica server 將 primary_slot_name 註解掉,接著在 primary server 運行這個指令:

1
SELECT pg_drop_replication_slot('myslot1');

這些大概就是 Physical Replication Slot 的功能。

如果你 archive_command 或是 physical replication slot 都不想做,但是又想要解決資料落差要怎麼辦呢?

你只有兩種選擇:

  1. 將 primary server 的 wal_keep_segments 設置的夠高,因為這個參數是設定 WAL file 的最小数量,這樣的話不需要維護被恨或是 disk 佔用一堆的情況發生,但這個不是一個可行的方案。因為如果 replica 落後太多超過這個數量還是會有資料落差的問題無法解決,請注意在 Postgres 13 後將這個參數改為 wal_keep_size 變成是指定每一個 WAL 檔案的大小而不是用數量去控制。
  2. 同步複製。沒錯,就是 Primary 的資料要跟 Replica 是同步的,而不是異步,想當然這會導致性能下降,一般來說我們使用 Primary 與 Replica 的架構不會採用這個方案的。

所以可以說這兩個選擇都不是很好的方案,會建議 archive_command 與 physical replication slot 並行,前者主要是為了備份,後者則是為了資料落差的問題。一起使用才是好方案。

Demo 設定 Physical Replication

接著就來 Demo 如何設定 Physical Replication,並且記錄下 9.6 ~ 11, 12 ~ 15 其中 replica server 的設定有什麼不同。

首先安裝 postgres,你可以是兩台 VM 一台是 Primary 一台是 Replica 都行,或是你要用 docker 也可以,另外 Physical Replication 只可以主版本的 PG 才可以互通喔,PG 9.6 是無法 PG 11 建立 Physical Replication 的

1
2
3
4
5
6
sudo apt update 
sudo apt install vim curl gpg gnupg2 software-properties-common apt-transport-https lsb-release ca-certificates
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc|sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list
sudo apt update
sudo apt install postgresql-11

雖然我這邊安裝 11 但底下也會說明如果安裝的是 12 要怎麼設定。

Primary Server

接著在 Primary Server 建立用來 replication 的 role

1
CREATE ROLE strmrepl WITH REPLICATION PASSWORD 'strmrepl' LOGIN;

接著設定 Primary Server 的 pg_hba.conf 加入以下的連線資訊

當然 method 用 md5 才是安全的,這邊 demo 用就直接用 trust 了,注意 ip 是填的是 replica server 的 ip。 /32 就代表 ip 要全部符合才可以。

1
host  replication  strmrepl  127.0.0.1/32  trust

再來設定 primary postgresql.conf 的內容

1
2
3
4
5
6
7
8
9
10
listen_addresses = '*'
port = 5432
wal_level = replica
max_wal_senders = 10 # 一次最多丟多少量的交易日誌
wal_keep_segments = 256 # 一次最多保留多少交易日誌

archive_mode = on # 是否開啟 archive 機制
archive_command = '/bin/true' # archive command 這樣寫的話代表 if true 的意思,為什麼要這樣寫?好處是之後如果你想要改 archive_command 可以動態修改設定而不用 restart database。
# 例如你可以這樣:
# archive_command 或是可以用常見的做法 archive_command = 'cp %p /archives/%f' 那對應的 restore_command 就會是 restore_command = 'cp /archives/%f %p'

如果你不想要用 archive_command 的方式來解決資料落差的問題,那你就在這邊多加入 create physical slot 的操作。

接著都設定好之後就 restart primary server 吧

Replica Server

而其實 9.6 ~ 11, 12 ~ 15 最主要差別的設定就是在 Replica Server 上。

一般來說,你建立 Replica Server 有個問題要解決,前面我們提到 Database System Identifier 的東西,這個是 Replica 與 Primary 互相認識的標誌,但是如果你只是安裝兩台 Postgres Server 在不同機器,因為跑過 initdb 他們兩台的標誌是不一樣的。

所以通常大家會說使用 pg_basebackup 官方提供的工具將 Primary Server 的 data copy 到另一台 Replica Server 的機器,也因為這樣的關係,需要先將 Replica Server 的 pgdata 先清空,在複製過去就能確保兩台的標誌是一樣的,此外帶來好處不外乎是兩邊的資料會接近同步,如果此時你的 Primary Server 有持續 online 接收 write 的請求,那此時兩邊在你 copy 好是會有資料落差,如果你沒設定 archive_command 或是 physical slot 一樣會是無法接上的。

指令如下:

1
pg_basebackup -h 127.0.0.1 -p 5432 -D /var/lib/postgresql/11/main -U strmrepl -v -P

指令細節請參考:https://www.postgresql.org/docs/current/app-pgbasebackup.html

唯一要提的是如果你加入 -R 指令那麼在 PG 11 以前會自動幫你產生 recovery.conf 在 pgdata 下

但是如果是 pg 12 之後,-R 指令變成是產生 standby.signal 在 pgdata 並且原本 recovery.conf 的參數搬過去到 postgresql.auto.conf 上了。

裡面的內容如下:

1
2
3
standby_mode = 'on'
primary_conninfo = 'host=127.0.0.1 port=5432 user=strmrepl'
trigger_file = '/tmp/pg_failover.trigger'

在 PG 11 之前參數是長這樣

但如果是 PG 12 之後參數會變成這樣

1
2
primary_conninfo = 'host=127.0.0.1 port=5432 user=strmrepl'
promote_trigger_file = '/tmp/pg_failover.trigger'

standby_mode 被自動產生出來的 standby.signal 給取代了,而 trigger_file 基本上就是 replica 要 promote 變成 primary 的觸發機制。

接著設定 postgresql.conf

1
2
3
4
listen_addresses = '*'
port = 5432
wal_level = replica
hot_standby = on

好了之後就可以 restart replica server

接著要看兩邊有沒有正確連上,可以透過在 Primary Server 跑以下指令:

1
SELECT * FROM pg_stat_replication;

以及在 Replica Server 跑以下指令:

1
SELECT * FROM pg_stat_wal_receiver;

之後就可以試試看在 Primary 做資料更動,並且 Replica 有沒有同樣收到這樣的資料更動的實驗了。

如果你沒設定 archive 或是 physical slot 你會發現你如果將 replica server shutdown 並且在 primary 做資料異動,你在開啟 replica 就發現 replica 連不上 primary 了。

順帶一提有沒有辦法連上,是會出現 log 訊息了,如果你沒做特別的設定或是你只是想在 stdout 看到 log 訊息可以這樣做:

1
pg_ctlcluster 11 main start -- --log ~/test.log

總結

這篇文章主要紀錄設定 Physical Replication 常會遇到的問題,因為版本的關係加上官方文件也沒有到很清楚,因此透過紀錄此篇文章可以幫助自己更理解設定的細節,之後再來談談如何 upgrade PG Cluster。