1. Cài đặt và Cấu hình Plugin Raft cho PostgreSQL
Chúng ta sẽ cài đặt extension pg_raft (hoặc patroni với Raft consensus) để biến các nút PostgreSQL độc lập thành một cụm (cluster) đồng bộ. Ở đây, tôi giả định bạn đã cài PostgreSQL 14+ và có 3 nút với hostname: node1, node2, node3.
Tại sao cần bước này: Raft là thuật toán consensus giúp các nút thống nhất về trạng thái dữ liệu (Leader/Follower) và ngăn chặn việc ghi dữ liệu vào nhiều nút cùng lúc (Split-brain). Extension này sẽ quản lý việc ghi WAL (Write-Ahead Log) vào journal của Raft.
Kết quả mong đợi: Extension pg_raft được load, bảng raft_log xuất hiện trong schema public, và các nút sẵn sàng nhận lệnh khởi tạo cluster.
Trước tiên, cài đặt package extension trên tất cả các nút (node1, node2, node3):
sudo apt-get update && sudo apt-get install -y postgresql-14-pg-raft
Trên CentOS/RHEL, lệnh tương đương là dnf install postgresql14-pg-raft.
Sau đó, thêm extension vào database template1 để áp dụng cho mọi database mới tạo:
sudo -u postgres psql -c "CREATE EXTENSION pg_raft;"
Kết quả: PostgreSQL trả về CREATE EXTENSION và bảng pg_raft xuất hiện khi bạn chạy \dt.
Cấu hình file pg_hba.conf để cho phép giao tiếp Raft
Raft cần giao tiếp qua TCP port riêng (mặc định 5432 cho PostgreSQL, nhưng Raft cần kết nối peer-to-peer qua socket hoặc port TCP tùy cấu hình). Chúng ta sẽ mở cổng 5432 cho traffic nội bộ.
Tại sao: Nếu không cấu hình pg_hba.conf, các nút không thể handshake để bầu chọn leader hoặc đồng bộ log.
Chỉnh sửa file /etc/postgresql/14/main/pg_hba.conf (đường dẫn có thể khác tùy distro, ví dụ /var/lib/pgsql/data/pg_hba.conf):
# Cho phép kết nối Raft giữa các nút trong cluster
host replication all 192.168.1.10/32 trust
host replication all 192.168.1.11/32 trust
host replication all 192.168.1.12/32 trust
# Cho phép kết nối quản trị từ localhost
local all all trust
Kết quả: File được lưu, cần restart PostgreSQL để áp dụng: sudo systemctl restart postgresql.
2. Khởi tạo Cluster và Định nghĩa Peers
Bước này xác định cấu trúc cluster: 3 nút, 2 nút là voter (có quyền bầu chọn), 1 nút là observer (chỉ đọc, không tham gia bầu chọn - tùy chọn, ở đây ta dùng 3 voter để đảm bảo quorum).
Tại sao: Raft cần một "config" ban đầu để biết ai là thành viên. Nếu không định nghĩa peers, leader không thể biết gửi log cho ai.
Kết quả mong đợi: Cluster được khởi tạo, một nút trở thành Leader, các nút còn lại là Follower và bắt đầu đồng bộ WAL.
Chạy lệnh khởi tạo cluster trên nút node1 (nút sẽ làm Leader đầu tiên):
sudo -u postgres psql -c "SELECT raft_bootstrap_cluster('my_cluster', ARRAY[
'{"id": 1, "address": "node1:5432", "voter": true}',
'{"id": 2, "address": "node2:5432", "voter": true}',
'{"id": 3, "address": "node3:5432", "voter": true}'
]);"
Lệnh này gọi function raft_bootstrap_cluster của extension, truyền tên cluster và danh sách JSON định nghĩa peers.
Sau khi chạy xong trên node1, trên node2 và node3, ta cần join vào cluster bằng cách chỉ định address của Leader (node1):
sudo -u postgres psql -c "SELECT raft_join_cluster('my_cluster', 'node1:5432');"
Kết quả: Node2 và Node3 chuyển sang trạng thái FOLLOWER và bắt đầu pull WAL từ Node1.
Verify trạng thái cluster
Trên bất kỳ nút nào, chạy lệnh kiểm tra trạng thái consensus:
sudo -u postgres psql -c "SELECT id, role, is_leader, last_applied_index FROM raft_status();"
Kết quả mong đợi: Một dòng có role là LEADER (thường là node1), hai dòng còn lại là FOLLOWER. Cột is_leader chỉ true ở 1 nút.
3. Thiết lập Cơ chế Bầu chọn và Xử lý Split-brain
Raft tự động xử lý bầu chọn khi Leader mất. Tuy nhiên, để ngăn Split-brain (hai leader cùng tồn tại), ta cần cấu hình quorum và election_timeout.
Tại sao: Trong mạng chia cắt (network partition), nếu 2 nhóm nút không liên lạc được, nhóm có quorum lớn hơn (thường là nhóm có Leader cũ hoặc nhóm có nhiều voter hơn) sẽ giữ quyền, nhóm kia tự động chuyển sang STANDBY hoặc READ-ONLY để tránh ghi dữ liệu mâu thuẫn.
Kết quả mong đợi: Khi Leader tắt, Follower bầu chọn Leader mới trong khoảng thời gian election_timeout (mặc định 1-3s). Không có 2 Leader cùng ghi dữ liệu.
Cấu hình tham số pg_raft trong file postgresql.conf (đường dẫn: /etc/postgresql/14/main/postgresql.conf):
# Thời gian chờ để bắt đầu bầu chọn (ms)
pg_raft.election_timeout = 1500
# Thời gian heartbeat giữa Leader và Follower (ms)
pg_raft.heartbeat_interval = 500
# Số lượng nút cần để đạt quorum (mặc định là majority, tức là 2 trong 3)
# Không cần set thủ công nếu cluster lẻ, nhưng để rõ ràng:
pg_raft.quorum_size = 2
# Cho phép tự động chuyển đổi role khi mất leader
pg_raft.automatic_leader_election = on
Kết quả: Sau khi sửa, restart PostgreSQL: sudo systemctl restart postgresql. Các tham số này sẽ áp dụng cho phiên bản chạy.
Mô phỏng mất Leader và kiểm tra bầu chọn
Tắt dịch vụ PostgreSQL trên node1 (Leader hiện tại):
sudo systemctl stop postgresql
Chờ 2-3 giây, sau đó kiểm tra trên node2 hoặc node3:
sudo -u postgres psql -c "SELECT id, role FROM raft_status();"
Kết quả mong đợi: Một trong các nút còn lại (node2 hoặc node3) đã chuyển sang LEADER. Trạng thái is_leader của node đó là true. Node1 khi bật lại sẽ tự động nhận diện mình là FOLLOWER và đồng bộ lại log từ Leader mới.
Để xử lý Split-brain cực đoan (ví dụ: 2 nhóm bị cắt mạng hoàn toàn), Raft sẽ tự động "bỏ phiếu" cho nhóm có quorum. Nhóm không có quorum sẽ tự động disable write. Bạn có thể kiểm tra bằng cách:
sudo -u postgres psql -c "SELECT pg_raft_is_leader();"
Nếu trả về false trên cả 2 nhóm bị cắt (trừ nhóm có quorum), nghĩa là cơ chế Split-brain đang hoạt động.
4. Cấu hình Replication Slots để đảm bảo Follower đồng bộ
Mặc dù Raft đồng bộ log, nhưng PostgreSQL vẫn cần replication slots để giữ WAL trên Leader cho đến khi Follower xác nhận đã nhận. Điều này ngăn Leader xóa WAL quá sớm gây mất dữ liệu nếu Follower chậm.
Tại sao: Trong môi trường phân tán, mạng có thể bị giật. Nếu không có slot, Leader có thể recycle WAL mà Follower chưa kịp nhận, dẫn đến mất dữ liệu vĩnh viễn (data loss).
Kết quả mong đợi: Các slot được tạo tự động theo tên của peer (ví dụ node2_slot, node3_slot). Cột replayed_lsn trong slot phải tăng lên theo thời gian.
Tạo replication slot thủ công cho từng follower (thực hiện trên Leader):
sudo -u postgres psql -c "SELECT pg_create_physical_replication_slot('node2_slot', true);"
sudo -u postgres psql -c "SELECT pg_create_physical_replication_slot('node3_slot', true);"
Tham số true nghĩa là slot này chỉ dành cho physical replication (WAL streaming), phù hợp với Raft.
Liên kết slot với Raft peer. Điều này thường được pg_raft tự động hóa, nhưng để đảm bảo, ta cấu hình trong postgresql.conf phần pg_raft (nếu extension hỗ trợ explicit slot mapping) hoặc kiểm tra xem slot đã được tạo bởi Raft chưa:
sudo -u postgres psql -c "SELECT slot_name, slot_type, active FROM pg_replication_slots();"
Kết quả mong đợi: Xuất hiện các slot node2_slot, node3_slot với active là true.
Xử lý trường hợp Follower bị trễ (Lag)
Để kiểm tra độ trễ đồng bộ, so sánh pg_last_wal_receive_lsn() và pg_last_wal_replay_lsn() trên Follower:
sudo -u postgres psql -c "SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), pg_last_wal_replay_lsn()) AS lag_bytes;"
Nếu lag_bytes > 0, nghĩa là Follower đang chậm. Nếu slot bị "stuck" (không active), Leader sẽ giữ WAL cho đến khi disk full. Cần restart follower hoặc debug mạng.
5. Kiểm tra Trạng thái Consensus và Log Consensus (WAL)
Bước cuối cùng là xác minh toàn bộ luồng dữ liệu: Dữ liệu ghi vào Leader -> Được ghi vào Raft Log -> Được đồng bộ sang Follower -> Follower apply vào database.
Tại sao: Đây là bằng chứng trực quan nhất cho tính nhất quán (consistency). Nếu dữ liệu trên Leader khác Follower, cluster đã bị lỗi.
Kết quả mong đợi: Dữ liệu ghi trên Leader xuất hiện tức thì trên Follower. Các entry trong raft_log có trạng thái COMMITTED.
Thực hiện test ghi dữ liệu trên Leader (node1 hoặc node đang làm Leader):
sudo -u postgres psql -c "CREATE TABLE test_raft_consistency (id serial, data text, created_at timestamp);"
sudo -u postgres psql -c "INSERT INTO test_raft_consistency (data) VALUES ('Raft Sync Test 1');"
sudo -u postgres psql -c "INSERT INTO test_raft_consistency (data) VALUES ('Raft Sync Test 2');"
Kết quả: Lệnh INSERT trả về INSERT 0 1.
Kiểm tra ngay lập tức trên Follower (node2 hoặc node3):
sudo -u postgres psql -c "SELECT * FROM test_raft_consistency;"
Kết quả mong đợi: Bảng và dữ liệu xuất hiện y hệt như trên Leader. Nếu không thấy, cluster chưa đồng bộ hoặc Follower bị lỗi.
Kiểm tra Raft Log và Consensus Index
Để xem dưới nắp (under the hood), kiểm tra bảng log của Raft:
sudo -u postgres psql -c "SELECT id, term, index, type, data, status FROM raft_log ORDER BY index DESC LIMIT 5;"
Kết quả mong đợi: Các entry gần nhất có status là COMMITTED. Cột term tăng lên mỗi lần có bầu chọn Leader mới. Cột index tăng liên tục.
So sánh last_applied_index giữa các nút để đảm bảo đồng bộ hoàn toàn:
sudo -u postgres psql -c "SELECT id, last_applied_index FROM raft_status();"
Kết quả: Giá trị last_applied_index phải giống hệt nhau trên tất cả các nút (Leader và Follower). Nếu khác nhau, dữ liệu chưa đồng bộ xong (lag).
Kiểm tra trạng thái WAL stream trong pg_stat_replication (dù Raft dùng cơ chế riêng, nhưng slot vẫn hoạt động):
sudo -u postgres psql -c "SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn FROM pg_stat_replication;"
Kết quả: state là streaming. Các LSN (write, flush, replay) phải tiến gần nhau, không có khoảng cách lớn.
Điều hướng series:
Mục lục: Series: Xây dựng Database phân tán an toàn với PostgreSQL, Raft và TDE
« Phần 2: Cài đặt và cấu hình PostgreSQL cơ bản cho từng nút
Phần 4: Cấu hình TDE (Transparent Data Encryption) cho dữ liệu nghỉ »