Bối cảnh & Kiến trúc
Sáu tháng trước, cơ sở dữ liệu PostgreSQL primary dung lượng 3.8 TB của chúng tôi bắt đầu quá tải trước các truy vấn báo cáo nặng. Đội ngũ BI chỉ cần cập nhật liên tục với độ trễ thấp từ đúng 4 bảng cốt lõi: orders, order_items, payments, và inventory. Trong khi đó, 170 bảng còn lại trong schema liên tục phát sinh khối lượng ghi rất lớn từ session store và audit log tạm thời.
Vào thời điểm đó, chúng tôi dựa vào giải pháp physical streaming replication tiêu chuẩn. Physical replication sao chép toàn bộ cluster chính xác từng byte ở cấp độ storage block. Dưới mức tải đỉnh điểm 12.000 lượt ghi mỗi giây, primary node tạo ra tới 400 GB Write-Ahead Logs (WAL) mỗi ngày. Node replica dùng cho analytics phải dồn gần như toàn bộ I/O của ổ NVMe chỉ để replay các block WAL thay vì thực thi truy vấn phân tích. Chúng tôi đang lãng phí tới 4 TB dung lượng đĩa đắt đỏ chỉ để truy vấn khoảng 65 GB dữ liệu nghiệp vụ thực tế.
Logical replication giải quyết triệt để vấn đề này bằng cách tách biệt việc luân chuyển dữ liệu khỏi các block đĩa vật lý bên dưới. Một thành phần cốt lõi có tên pgoutput sẽ giải mã (decode) các bản ghi WAL thành từng tuple DML riêng lẻ ở cấp độ hàng (INSERT, UPDATE, DELETE). Điều này cho phép bạn xuất bản (publish) các bảng cụ thể, stream các thay đổi giữa những phiên bản PostgreSQL major khác nhau, và xây dựng các index tối ưu riêng trên replica mà không hề ảnh hưởng đến cơ sở dữ liệu primary.
Việc chuyển đổi node analytics sang logical replication đã giảm thông lượng ghi đĩa trên replica từ 420 MB/s xuống dưới 25 MB/s. Đồng thời, dung lượng lưu trữ trên replica cũng giảm đến 92%. Tuy nhiên, logical replication lại đi kèm với những thách thức vận hành riêng. Nếu một replica bị mất kết nối mà không được giám sát, replication slot tương ứng sẽ tích tụ các file WAL trên publisher cho đến khi dung lượng đĩa chạm ngưỡng 100%.
Yêu cầu tiên quyết & Thiết lập môi trường
Logical replication sử dụng tính năng gốc có sẵn của PostgreSQL từ phiên bản 10, nghĩa là không cần thêm daemon bên ngoài hay extension của bên thứ ba. Trong bài hướng dẫn này, cả hai node đều chạy PostgreSQL 16 trên Ubuntu 22.04 LTS. Publisher chứa database production tại địa chỉ 192.168.1.10, còn subscriber chạy tại 192.168.1.20.
Trước tiên, hãy đảm bảo PostgreSQL đã khởi chạy trên cả hai máy chủ:
# Chạy trên cả Publisher (192.168.1.10) và Subscriber (192.168.1.20)
sudo apt update && sudo apt install -y postgresql-16 postgresql-contrib
sudo systemctl enable postgresql
sudo systemctl start postgresql
Trên publisher, tạo một user chuyên dụng với quyền tối thiểu. Tài khoản này cần thuộc tính REPLICATION và quyền đọc trên schema được publish:
-- Thực thi trên Publisher (database app_prod)
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'SecretClusterPass2026!';
GRANT CONNECT ON DATABASE app_prod TO replicator;
GRANT USAGE ON SCHEMA public TO replicator;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO replicator;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO replicator;
Tiếp theo, thêm IP của subscriber vào danh sách trắng (whitelist) trong tệp /etc/postgresql/16/main/pg_hba.conf trên publisher:
# Cho phép kết nối logical replication từ máy chủ Subscriber
host app_prod replicator 192.168.1.20/32 scram-sha-256
Các bước cấu hình chi tiết
Để hệ thống replication hoạt động, chúng ta cần cấu hình bộ giải mã WAL trên publisher, khai báo publication, và khởi tạo subscription worker.
1. Thiết lập cấu hình Engine trên Publisher
Mở tệp /etc/postgresql/16/main/postgresql.conf trên publisher. Đảm bảo cấp độ WAL hỗ trợ logical decoding và cấu hình đủ số lượng replication worker:
# /etc/postgresql/16/main/postgresql.conf trên Publisher
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
listen_addresses = '*'
Vì việc thay đổi wal_level yêu cầu khởi động lại toàn bộ server, hãy chạy lệnh sau:
sudo systemctl restart postgresql
2. Khởi tạo Publication
Logical replication chỉ stream các thay đổi về dữ liệu, không stream Data Definition Language (DDL). Bạn phải tự định nghĩa cấu trúc bảng thủ công ở cả hai phía trước khi bắt đầu replication. Trong ví dụ này, chúng ta chỉ replicate hai bảng orders và order_items:
-- Trên Publisher: Tạo schema nghiệp vụ mẫu
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
customer_id INT NOT NULL,
order_status VARCHAR(32) NOT NULL,
total_amount NUMERIC(12,2) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE order_items (
item_id BIGSERIAL PRIMARY KEY,
order_id BIGINT REFERENCES orders(order_id),
sku VARCHAR(64) NOT NULL,
quantity INT NOT NULL,
price NUMERIC(10,2) NOT NULL
);
-- Chỉ publish các bảng được chỉ định
CREATE PUBLICATION analytics_pub FOR TABLE orders, order_items;
3. Cấu hình Subscriber & Đồng bộ dữ liệu ban đầu
Trên node subscriber, cấp phát tài nguyên cho background worker trong tệp postgresql.conf:
# /etc/postgresql/16/main/postgresql.conf trên Subscriber
max_logical_replication_workers = 8
max_sync_workers_per_subscription = 4
Tải lại cấu hình mới bằng lệnh sudo systemctl reload postgresql. Tạo các bảng tương ứng trên subscriber, có thể lược bỏ các khóa ngoại không cần thiết hoặc bổ sung thêm index phục vụ phân tích theo nhu cầu. Sau đó kích hoạt subscription:
-- Trên Subscriber: Định nghĩa schema tương ứng
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
customer_id INT NOT NULL,
order_status VARCHAR(32) NOT NULL,
total_amount NUMERIC(12,2) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE order_items (
item_id BIGSERIAL PRIMARY KEY,
order_id BIGINT REFERENCES orders(order_id),
sku VARCHAR(64) NOT NULL,
quantity INT NOT NULL,
price NUMERIC(10,2) NOT NULL
);
-- Tạo subscription và bắt đầu đồng bộ snapshot
CREATE SUBSCRIPTION analytics_sub
CONNECTION 'host=192.168.1.10 port=5432 dbname=app_prod user=replicator password=SecretClusterPass2026!'
PUBLICATION analytics_pub
WITH (copy_data = true, create_slot = true);
Thiết lập copy_data = true yêu cầu Postgres sao chép snapshot dữ liệu ban đầu của bảng trước khi áp dụng các thay đổi từ WAL theo thời gian thực. Với bảng orders gồm 45 triệu bản ghi của chúng tôi, quá trình truyền dữ liệu ban đầu này mất khoảng 14 phút qua mạng nội bộ 10 Gbps.
4. Xử lý xung đột Replication
Khác với physical replica, database trên subscriber hoàn toàn có thể ghi được (writable). Sự linh hoạt này đi kèm với rủi ro tiềm ẩn: nếu một script cục bộ chèn một bản ghi có khóa chính trùng với dữ liệu đang được đẩy sang từ publisher, quá trình replication sẽ lập tức dừng lại.
Khi xảy ra lỗi, apply worker sẽ liên tục thử lại (retry) mỗi 5 giây. Vòng lặp này làm tràn ngập log hệ thống của Postgres và khiến độ trễ replication tăng vọt. Hãy kiểm tra /var/log/postgresql/postgresql-16-main.log trên subscriber để xác định LSN commit đang gặp sự cố (ví dụ: 0/3142A90).
-- Cách A (PostgreSQL 15+): Bỏ qua transaction bị xung đột một cách an toàn
ALTER SUBSCRIPTION analytics_sub DISABLE;
ALTER SUBSCRIPTION analytics_sub SKIP (lsn = '0/3142A90');
ALTER SUBSCRIPTION analytics_sub ENABLE;
-- Cách B: Tự xử lý bản ghi xung đột trên Subscriber
DELETE FROM orders WHERE order_id = 9042;
ALTER SUBSCRIPTION analytics_sub ENABLE;
Tốt nhất là chủ động ngăn ngừa xung đột trước khi chúng xảy ra. Hãy hạn chế quyền trên subscriber bằng cách chỉ cấp quyền SELECT cho người dùng chạy báo cáo, và chỉ giữ quyền ghi duy nhất cho role của replication worker.
Giám sát & Quản lý Replication Slot
Một replication slot bị bỏ rơi chính là một sự cố tiềm tàng. Publisher đảm bảo tính toàn vẹn dữ liệu bằng cách giữ lại tất cả các file WAL từ giá trị restart_lsn của slot trở đi. Nếu subscriber gặp sự cố và ngắt kết nối kéo dài, các file WAL sẽ tiếp tục tích tụ không giới hạn cho đến khi ổ đĩa cạn kiệt hoàn toàn.
1. Giám sát Replication Lag và Dung lượng Slot
Chạy truy vấn chẩn đoán sau trên publisher để kiểm tra trạng thái slot và dung lượng đĩa đang bị giữ lại:
-- Kiểm tra trạng thái slot và dung lượng WAL đang lưu giữ
SELECT
slot_name,
plugin,
active,
wal_status,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;
-- Giám sát độ trễ replay theo thời gian thực
SELECT
client_addr,
application_name,
state,
sync_state,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn)) AS send_lag,
pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) AS replay_lag
FROM pg_stat_replication;
2. Bảo vệ Ổ đĩa Publisher
Để đảm bảo một subscriber bị hỏng không làm tê liệt toàn bộ primary cluster, hãy đặt hạn mức trần cho việc giữ lại WAL trong tệp postgresql.conf trên publisher:
# Áp mức trần cứng 64 GB cho việc lưu giữ WAL trên mỗi slot
max_slot_wal_keep_size = 65536MB
Nếu độ trễ replication vượt quá 64 GB, Postgres sẽ vô hiệu hóa slot đó và chuyển wal_status sang trạng thái lost. Khi đó, publisher sẽ giải phóng và tái sử dụng các file WAL cũ để bảo vệ dung lượng đĩa primary. Subscriber sau đó sẽ phải đồng bộ lại từ đầu, nhưng cơ sở dữ liệu production của bạn vẫn luôn an toàn trực tuyến.
3. Thu hồi các Slot không còn sử dụng
Luôn xóa subscription từ phía subscriber bằng lệnh DROP SUBSCRIPTION analytics_sub;. Nếu máy chủ subscriber đã bị hủy hoặc ngừng hoạt động mà chưa dọn dẹp, hãy xóa trực tiếp slot mồ côi ngay trên publisher:
-- Giải phóng dung lượng lưu giữ WAL ngay lập tức bằng cách xóa slot bị bỏ rơi
SELECT pg_drop_replication_slot('analytics_sub');
Logical replication mang lại cho các đội ngũ kỹ thuật khả năng cô lập dữ liệu chuẩn xác mà không phải chịu gánh nặng chi phí hạ tầng lớn như streaming toàn bộ cluster. Chỉ cần duy trì giám sát replication slot và giới hạn dung lượng WAL lưu trữ, hệ thống pipeline phân tích dữ liệu downstream của bạn sẽ luôn vận hành ổn định trong nhiều năm.
