PostgreSQL Bài 49: Tích hợp PgBouncer và Patroni để định tuyến Read/Write trong cụm Master-Slave
Khi xây dựng kiến trúc High Availability (HA) với Patroni (đã học ở Bài 34) và Connection Pooling với PgBouncer (Bài 36), chúng ta gặp phải một bài toán hóc búa về kiến trúc mạng: Khi Patroni thực hiện Failover (chuyển một máy Slave thành Master mới), làm sao PgBouncer biết IP mới của Master để trỏ tới? Và làm sao để ứng dụng Backend tự động tách luồng Ghi (Write) vào Master và luồng Đọc (Read) vào các máy Slave mà không bị lỗi kết nối?

1. Vấn đề của PgBouncer độc lập trong cụm HA
PgBouncer vốn là một proxy tĩnh. File cấu hình pgbouncer.ini yêu cầu bạn khai báo cứng (hardcode) địa chỉ IP của database đích:
Ini, TOML
[databases]
my_db = host=192.168.1.10 port=5432
Nếu máy chủ 192.168.1.10 (đang là Master) bị sập đột ngột, Patroni thông qua sự đồng thuận của etcd sẽ lập tức thăng cấp máy Slave (192.168.1.11) lên làm Master mới. Tuy nhiên, PgBouncer không có cơ chế tự động nhận biết sự thay đổi này. Nó vẫn sẽ cố đẩy các câu lệnh INSERT/UPDATE vào IP cũ đã chết, gây sập toàn bộ luồng ghi của ứng dụng.
Để giải quyết, chúng ta không bao giờ cấu hình PgBouncer trỏ trực tiếp vào IP vật lý của PostgreSQL. Thay vào đó, chúng ta phải chèn thêm một lớp Load Balancer (thường dùng HAProxy) có khả năng giám sát trạng thái trực tiếp từ Patroni.
2. Mô hình định tuyến Read/Write (Read/Write Split) 4 Lớp
Để tận dụng tối đa tài nguyên phần cứng của cụm Master-Slave, ứng dụng Backend cần thiết lập 2 connection pool (hồ bơi kết nối) riêng biệt:
-
Write Pool (Luồng Ghi): Dành cho các lệnh
INSERT,UPDATE,DELETE,DDL. Bắt buộc phải đi vào Master. -
Read Pool (Luồng Đọc): Dành cho các lệnh
SELECTphân tích hoặc hiển thị danh sách. Có thể đi vào Master, hoặc chia đều (Round-robin) cho các máy Slave.
Dòng chảy dữ liệu sẽ đi qua 4 lớp kiến trúc như sau:
-
Ứng dụng Backend: Mở kết nối đến PgBouncer thông qua 2 bí danh cơ sở dữ liệu (alias) khác nhau.
-
PgBouncer (Lớp Pooling): Tiếp nhận hàng ngàn kết nối từ Backend, nén lại thành vài chục kết nối và đẩy sang HAProxy.
-
HAProxy (Lớp Định tuyến thông minh): Lắng nghe ở 2 cổng (ví dụ: cổng 5000 cho Write, cổng 5001 cho Read). Nó liên tục rà quét (Health-check) API của Patroni để biết chính xác node nào là Master, node nào là Slave ở từng phần nghìn giây.
-
Patroni + PostgreSQL: Xử lý và trả về kết quả.
3. Cấu hình HAProxy Health-check qua API của Patroni
Chìa khóa của toàn bộ hệ thống nằm ở việc Patroni cung cấp sẵn các API endpoint siêu nhẹ dành riêng cho Load Balancer gọi vào kiểm tra:
-
Endpoint
GET /master: Trả về mã HTTP 200 nếu node đó đang là Master. Trả về HTTP 503 nếu là Slave. -
Endpoint
GET /replica: Trả về mã HTTP 200 nếu node đó đang là Slave khỏe mạnh.
Mẫu file cấu hình haproxy.cfg:
Đoạn mã
# ----- KHỐI ĐỊNH TUYẾN LUỒNG GHI (WRITE) -----
listen master_write
bind *:5000
mode tcp
# HAProxy tự động gọi API /master để kiểm tra
option httpchk GET /master
# Cổng 8008 là cổng API mặc định của Patroni
server pg1 192.168.1.10:5432 check port 8008
server pg2 192.168.1.11:5432 check port 8008
# ----- KHỐI ĐỊNH TUYẾN LUỒNG ĐỌC (READ) -----
listen replicas_read
bind *:5001
mode tcp
balance roundrobin
# HAProxy tự động gọi API /replica để kiểm tra
option httpchk GET /replica
server pg1 192.168.1.10:5432 check port 8008
server pg2 192.168.1.11:5432 check port 8008
Nhờ cơ cấu này, nếu pg1 sập, HAProxy sẽ tự động chặn cổng 5000 đến pg1 và mở cổng 5000 đến pg2 (do pg2 vừa được Patroni đưa lên làm Master). Ứng dụng Backend hoàn toàn không phải đổi chuỗi kết nối.
4. Cấu hình PgBouncer liên kết với HAProxy
Lúc này, cấu hình của PgBouncer trở nên vô cùng đơn giản và an toàn. Bạn chỉ cần định nghĩa 2 cơ sở dữ liệu ảo trỏ về 2 cổng của HAProxy.
Ini, TOML
[databases]
# Mọi luồng Ghi từ ứng dụng sẽ đi qua cổng 5000 (HAProxy master_write)
my_db_write = host=127.0.0.1 port=5000 dbname=my_db
# Mọi luồng Đọc từ ứng dụng sẽ đi qua cổng 5001 (HAProxy replicas_read)
my_db_read = host=127.0.0.1 port=5001 dbname=my_db
[pgbouncer]
listen_port = 6432
pool_mode = transaction
default_pool_size = 100
Trong thực tế triển khai vi dịch vụ (Microservices), PgBouncer thường được cài đặt dưới dạng Sidecar Container nằm ngay cạnh ứng dụng Backend. Backend chỉ cần kết nối vào localhost:6432/my_db_write, việc tìm đường đến Master PostgreSQL nằm ở Data Center nào, IP ra sao, đổi IP khi sập thế nào sẽ do cặp đôi PgBouncer và HAProxy lo liệu toàn bộ.
All rights reserved