pgpool-ll stream replication 架設教學
延續上次 Postgres Stream Replication 的筆記,這次來講講 pgpool-ll,這個東西可以想成是 postgres 的 proxy,並且可以做到 load balance 的效果。
在升級 Postgres 的版本之前,如果你的 Postgres 前面有掛 pgpool-ll,那麼升級後,就必須去更改 pgpool-ll 的設定,因此這邊決定會先來幾篇文章講講 pgpool-ll 的相關設定。
我們知道 Postgres 如果採用 Stream Replication 的話,是單個 primary 跟 多台 replica 的架構,而 primary 可以接受 read/write,但是 replica 只能接受 read。如果沒有 pgpool-ll 這種去做 read/write 的分配的話,我們就目前在自己的 service 的手動指定 read 要去哪一個 replica 的 pg,write 則是要去哪一個 primary 的 pg。
這樣其實很不方便,所以就需要 pgpool-ll 這種 proxy 作統一個管理。
pgpool-ll 介紹
先來講一下 pgpool 有第一代的版本,不過提供的功能非常少,現在也不會有人想用 pgpool-l 原因在於第一代沒有支援 load balance 及 自動故障轉移還有 priamry /replica 的模式支持。
第一代是還有提供 logical replication 的功能,不過基本上如果要專業的 logical replication 方案,我們會採用 pglogical 或者是 Postgres 原生的 logical replication。
而 pgpool-ll 跟 pgbouncer 兩個都是 Postgres 常見的 Connection Pooling 的解決方案。但兩者提供的功能還是有些區別,關於 pgbouncer 之後再開文章來介紹。
pgpool-ll 本身除了 connection pooling 的管理,其實還提供很多功能,例如 pgpool-ll 可以幫你做到以下事情:
-
High-availability:也就是 failover 的功能,例如如果背後的 Postgres Cluster 的 Primary 故障了,pgpool-ll 會透過 health check 去做檢查,一但有問題會 promote replica Postgres to primary。也就是我們叫做自動故障轉移。
另外為了避免 pgpool-ll 遇到單點故障的問題,pgpool-ll 可以支援架設 cluster 的方式,架設多個 pgpool-ll nodes,採用 quorum algorithm 來解決 split brain 等 problem 產生。
-
Automated load balancing for primary and replicas:在 stream replication mode 下的 pgpool-ll 可以自動將 write/read 的 query 做 load balance 的效果,也是我們最期望想要的功能。
-
Online Recovery:假設如果你的 primary 故障了,使用自動故障轉移機制,你可以將故障的點透過 Online Recovery 的功能將故障的節點的資料 recovery 回來,並且當作新的 replica PG
-
In Memory Query Cache:例如會 cache 對應的 read query,而不會經過 Postgres,那當然會提高 read 的 performance。
安裝 pgpool-ll
目前 pgpool-ll 的最新版為 4.4.1。
- 在 mac 安裝的話可以透過 brew 很方便:
1 | brew install pgpool-ii |
-
或者是可以直接透過官方網站下載 source code 來進行 build:https://www.pgpool.net/mediawiki/index.php/Downloads
1
2
3tar xf pgpool-II-4.4.1.tar.gz
# 執行官方提供的腳本,並且可以給予對應的參數,可參考:https://www.pgpool.net/docs/44/en/html/install-pgpool.html
./configure -
在 debian 上安裝:
1
apt-get install pgpool2
Demo Streaming Replication For pgpool-ll
這邊 Demo 完整的如何架設出 Postgres Cluster With Streaming Replication 並且透過 pgpool 來進行 load balance。
這邊用 Postgres 12 來做示範:
-
設定 priamry db
-
Initdb
1
initdb -D /tmp/primary_db;
-
設定 postgresql.conf (這邊簡單設定)
1
2
3listen_addresses = '*'
port = 5432
wal_level = replica -
啟動 postgres
1
pg_ctl -D /tmp/primary_db start
-
登入 psql 建立 replication user
1
2
3psql -p 5432 postgres
# 原始的 plain password 是 repuserrepuser
CREATE USER repuser REPLICATION LOGIN PASSWORD 'md58ab1a75fe519fbd497653a855134aef7';-
因為給 md5 password 需要在 prefix 上加入 md5,原因來自於:
For an MD5 encrypted password,
rolpassword
column will begin with the stringmd5
followed by a 32-character hexadecimal MD5 hash. The MD5 hash will be of the user’s password concatenated to their user name. For example, if userjoe
has passwordxyzzy
, PostgreSQL will store the md5 hash ofxyzzyjoe
.https://www.postgresql.org/docs/current/catalog-pg-authid.html
另外還有 Postgres 對於 md5 的格式是 md5 (password+user),所以不要搞錯了,如果你是將 password 自行 md5 後再丟進去,之後你會發現登入不進去,因為格式要求與 Postgres 的不同。
另外如果你沒有加 md5 的 prefix,Postgres 只會當作照存原始的 password,也就是你丟進去的:8ab1a75fe519fbd497653a855134aef7,登入的時候也是要同樣輸入這串 8ab1a75fe519fbd497653a855134aef7 才可以登入成功。
當然你 password 這邊設定 md5 在 pg_hba.conf 也是要改成用 md5 來登入才可以。
-
產生 md5 的方式有一下幾種方式 ref:
Linux:
1
2echo -n "md5"; echo -n "repuserrepuser" | md5sum | awk '{print $1}'
md58ab1a75fe519fbd497653a855134aef7NOTE: The -n is critical to avoid including the newline character in your hash!
MacOS:
1
2echo -n "md5"; md5 -qs "repuserrepuser"
md58ab1a75fe519fbd497653a855134aef7Python 2:
1
2
3>>> import hashlib
>>> print("md5" + hashlib.md5("repuserrepuser").hexdigest())
md58ab1a75fe519fbd497653a855134aef7Python 3:
as above, but use binary strings
1
2print("md5" + hashlib.md5(b"repuserrepuser").hexdigest())
md58ab1a75fe519fbd497653a855134aef7
-
-
設定 pg_hba.conf
1
2
3
4
5
6# 如果想測試 psql 登入是不是可以用 md5 登入進去,可以將 local Type 的 Method 改成 md5
# local all all md5
# 這個設定除了安全的考量之外,另外可以拿來測試 pgpool 對 postgres 進行連線的時候也是需要 md5 的驗證
host all all 127.0.0.1/32 md5
# 這個是為了讓 replica 可以與 primary 進行連線
host replication repuser 127.0.0.1/32 md5 -
重啟 postgres
1
pg_ctl -D /tmp/primary_db restart
-
-
設定 replica db
-
透過 pg_basebackup 來設定
1
2pg_basebackup -h localhost -U repuser --checkpoint=fast \
-D /tmp/replica_db/ -R --slot=some_name -C -W --port=5432 -
設定 postgresql.conf
1
2listen_addresses = '*'
port = 5433 -
啟動 postgres
1
pg_ctl -D /tmp/replica_db start
接著如果看到這些 log 就代表有正常開啟 stream replication
1
2
3
4
52023-01-08 16:39:16.008 CST [34555] LOG: entering standby mode
2023-01-08 16:39:16.012 CST [34555] LOG: redo starts at 0/5000028
2023-01-08 16:39:16.013 CST [34555] LOG: consistent recovery state reached at 0/5000100
2023-01-08 16:39:16.013 CST [34554] LOG: database system is ready to accept read only connections
2023-01-08 16:39:16.024 CST [34559] LOG: started streaming WAL from primary at 0/6000000 on timeline 1
-
-
設定 pgpool
pgpool 預設的設定檔等位置會在 /usr/local/etc 裡面
-
設定 pgpool.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30# 將官方提供 sample 檔案複製一份
cp pgpool.conf.sample pgpool.conf
backend_clustering_mode = 'streaming_replication'
listen_addresses = '*'
port = 9999
pcp_listen_addresses = 'localhost'
pcp_port = 9898
backend_hostname0 = '127.0.0.1'
backend_port0 = 5432
backend_weight0 = 0
backend_data_directory0 = '/tmp/primary_db/'
backend_hostname1 = '127.0.0.1'
backend_port1 = 5433
backend_weight1 = 1
backend_data_directory1 = '/tmp/replica_db/'
enable_pool_hba = on
pool_passwd = 'pool_passwd'
log_statement = on
log_per_node_statement = on
pid_file_name = 'pgpool.pid'
sr_check_user = 'repuser'
health_check_period = 10
health_check_user = 'repuser'-
在 streaming_replication 模式下,backend_hostname0 會視為是 primary postgres,而之後填的 backend_hostname1 會視為是 replica postgres
-
pcp 是 pgpool 本身另外一個接口,可以使用 pgpool 提供 pcp 相關 tool 來操作 pgpool,例如:pcp_stop_pgpool 用來停止 pgpool。相關 pcp 工具可以參考:https://tatsuo-ishii.github.io/pgpool-II/current/pcp-commands.html
那麼 pcp 這邊也需要身份驗證,相關的 username 與 password 對應需要在 pcp.conf 設定:
1
2# USERID:MD5PASSWD
postgres:e8a48653851e28c69d0506508fb27fc5裡面會有預設的 username => postgres 跟 password => postgres,密碼必須透過 md5 來進行 hash。
也可以透過 .pcppass file 來設定 username 與 password 的對應關係
-
backend_weight 是比重越大,則 load balance 的佔比更大,如果將 primary postgres 的 weight 設為 0,那會變成所有的 write 只會到 primary,所有的 read 只會到 replica,你的 primary 將不接受 read 請求。
這邊主要是根據需求再去做調整就好,一般情況如果希望 priamry 跟 replica 都能接受 read 請求那就都設為 weight = 1 就可以了。
-
pgpool 本身也有做類似於 postgres hba 的身份驗證機制:
enable_pool_hba = on 是代表啟用 hba 身份驗證機制,pool_passwd 填的是檔案的位置,裡面的檔案內容代表的是 hba 內記錄的 username 與 password 的對應關係。
但要記住的是 pgpool 的 hba 是 client 與 pgpool 的驗證,不關後面的 postgres 的事情,也就是說 client 端到真正後面的 postgres 會經過兩次的驗證,一個 pgpool 的 hba 一個是 postgres 的 hba。因此這兩邊的 hba 會必須設定一樣。
這邊一樣 pgpool hba 有提供 postgres md5 的方式來驗證,我們可以透過 pgpool 提供的工具:pg_md5 來產生 username 與 password 的對應關係,再將其 md5 後存入 pool_passwd 檔案裡面。
1
pg_md5 --config-file=/usr/local/etc/pgpool.conf --md5auth --username=repuser repuser
以我們剛才的情境,我們的 health check user 跟 sr_check_user 是 repuser 所以要加上這個設定。如果你沒加上這個設定當你啟動 pgpool 會出現這種錯誤:
1
22023-01-14 15:30:07.043: main pid 9064: LOG: find_primary_node: make_persistent_db_connection_noerror failed on node 0
2023-01-14 15:30:07.046: main pid 9064: LOG: find_primary_node: make_persistent_db_connection_noerror failed on node 1而 Postgres 那邊會噴出的錯誤是:
1
2
3Connection matched pg_hba.conf line 90: "host all all 127.0.0.1/32 md5"
2023-01-14 15:33:21.714 CST [9580] FATAL: password authentication failed for user "repuser"
2023-01-14 15:33:21.714 CST [9580] DETAIL: Password does not match for user "repuser".原因就在於 pgpool 嘗試問 Postgres 存不存在,但是是用 repuser 的身份去問的,但你又沒有設定 pool_passwd 的相關設定,那自然問訪問 Postgres 密碼失敗。被擋在外面。
另外情況是你 sr_check 過了,但你 health_check_user 是用另外一個 user 身份的話,當你啟動 pgpool,會出現 health check 的錯誤:
1
2
32023-01-14 15:38:05.848: health_check pid 10105: LOG: process started
2023-01-14 15:38:05.850: health_check0 pid 10104: LOG: health check failed on node 0 but failover is disallowed for the node
2023-01-14 15:38:05.850: health_check1 pid 10105: LOG: health check failed on node 1 but failover is disallowed for the node所以總得來說,sr_check_user 跟 health_check_user 一定都要在 pool_passwd 與 postgres 兩邊的密碼設定一樣且 pool_hba.conf 跟 postgresql.conf 設定也要一致,例如都要是 md5 method 來進行訪問。
所以在我們的例子下,pool_hba.conf 設定如下:
1
2# IPv4 local connections:
host all all 127.0.0.1/32 md5 -
log_statement 跟 log_per_node_statement 是這邊為了 demo 方便看,所以將 log 的機制打開,在 production 環境要再斟酌
-
設定 pid_file_name 如果不設定的話,原本的路徑會需要自己先 mkdir /var/run/pgpool,這邊為了方便設定就將路徑改為與設定檔同路徑就可以了
-
sr_check_user 設定 stream replication 的 user 名稱,pgpool 會透過這個 user 去檢查目前 stream replication 的相關進度所以也可以設定 sr_check_period 的檢查頻率
-
health_check_period 跟 health_check_user 用來定期檢查底下 Postgres 的健康狀況如何
這些設定檔都搞定後,就可以啟動 pgpool:
1
2
3
4
5
6
7
8
9
10# 如果直接跑 pgpool 是會直接運行在 background 但是不會 print logs 因為 detaches the terminal
pgpool
# 如果要顯示 log 且運行在 background 要這樣
pgpool -n &
# 如果要將 log 導向 file, -d 代表開啟 debug message log
pgpool -n -d > /tmp/pgpool.log 2>&1 &
# 要停止的話,這樣是屬於 graceful shutdown pgpool 會等待 client 斷開連接,然後自行終止
pgpool stop
# 想要強制關閉
pgpool -m fast stopok,接著要來體驗一下 read/write 分離的 load balance 的效果之前:
在思考一件事情,我們的 application 一定會需要連線到 pgpool 再到 postgres,那本身 application 的 postgres user 也是要另外設定在 pgpool 跟 postgres 上的。
所以我們在 primary server 上 create user:
1
2# plain password 就是 kennykenny
CREATE USER repuser REPLICATION LOGIN PASSWORD 'md565d66c00e5b4c41450f47d900d549297';再來就是要在 pool_passwd 設定:
1
pg_md5 --config-file=/usr/local/etc/pgpool.conf --md5auth --username=kenny kenny
接著我們就可以透過 psql 進入 pgpool 裡面:
1
2
3
4
5
6
7
8psql -p 9999 postgres
CREATE TABLE users (id int primary key);
# pgpool 對應的 log 可以發現他是將上面 create table 送往 node id 0 也就是我們的 priamry pg
2023-01-14 16:07:11.917: psql pid 13069: LOG: DB node id: 0 backend pid: 13185 statement: CREATE TABLE users (id int primary key);
# 接著 read
SELECT * FROM users;
# pgpool 對應的 log 可以發現他是將上面的 select 送到 node id 1 也就是我們的 replica pg
2023-01-14 16:08:09.951: psql pid 13069: LOG: DB node id: 1 backend pid: 13186 statement: SELECT * FROM users;根據簡單的測試結果就可以知道 read/write 有幫我們做 load balance 的效果。那基本上如果你有多台 replica pg,本身也會幫你做 read 的 load balance 的效果,他會根據你所設定的每個 PG 的 weight 按照比例去分配。
-
-
總結
好的,算是做個有點詳細的筆記,關於 Postgres 與 pgpool 的基礎設定,以及展示了 load balance 的效果,關於 pgpool 的文檔讀起來都還算輕鬆。所以值得一看~
還有一篇 pgpool 常見的錯誤的原因及解決方案可以參考,挺棒的。