Bài toán phải upgrade hạ tầng thì chắc không lạ nữa, tôi cũng thích đọc tech blog dạo trước cũng có nhiều thắc mắc là mấy doanh nghiệp hàng đầu, hay doanh nghiệp lớn một chút chẳng hạn họ làm kiểu gì mà upgrade lên giảm thiểu tối đa downtime mà đặc biệt upgade hệ thống đang chạy trên production.

Cái việc tác động vào đêm chắc nhiều anh em đã biết rồi, bank hay các hệ thống nhiều nhiều người dùng, đòi SLA cao tại Việt Nam thường sẽ vậy (chưa làm với nước ngoài nên cũng chẳng biết).
Tôi cũng lab để upgrade Postgres cho hệ thống đang chạy production, tại mỗi lần đụng DB là dừng service khá lâu. Xong có nghiên cứu thấy logical replication hợp lý. Làm đúng quy trình thì app vẫn chạy, chỉ cần dừng các thao tác ghi dữ liệu (INSERT, UPDATE) trong một thời gian rất ngắn ngay tại thời điểm thực hiện ccutover sang database mới. Thấy cũng hiệu quả và vừa làm vừa note, mọi người có thể tham khảo nếu gặp nhé hay ai có cái hay hơn share cho cộng đồng cùng học hỏi là tươm.
Mục tiêu
- Cutover trong 1 đến 5 phút với rủi ro thấp.
- Không mất dữ liệu.
- Đo được replication lag và xác thực data consistency trước khi chuyển.
- Rollback Plan rõ ràng nếu phát sinh lỗi.
Kiến trúc mẫu để hình dung
- Source: Postgres 14 (do Patroni quản lý hoặc single tùy mọi người nhé), cổng 5432.
- Destination: Postgres 15 (phần này dev bên tôi yêu cầu anh em muốn upgrade hơn cũng tương tự vậy), cổng 5433, dữ liệu trống.
- App: Kết nối qua PgBouncer để hạn chế số connection và đổi endpoint cho gọn.
- Monitoring:
postgres_exporter,pg_stat_replication,pg_stat_subscription,wal_level=logical.
Chuẩn bị kiểm tra
- Kiểm tra disk và bandwidth giữa hai DB. WAL sẽ được stream iên tục.
- Bật
wal_level=logicaltrên source. - Trên source, tạo user replication riêng, chỉ cấp quyền cần thiết.
- Đảm bảo replica destination có collation và timezone tương thích.
- Tạo bộ test đọc ghi nhỏ ở ứng dụng để xác nhận sau cutover.
Cấu hình trên source
postgresql.conf
wal_level = logical
max_wal_senders = 10
max_replication_slots = 10
pg_hba.conf cho phép kết nối từ DB destination
host replication repl_user 192.168.1.0/24 scram-sha-256
host all repl_user 192.168.1.0/24 scram-sha-256
Tạo role và publication trên source
-- trên PG14
CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'StrongPass!';
-- nếu app schema là public hoặc custom, chọn đúng schema
-- khuyến nghị publication theo schema để kiểm soát
CREATE PUBLICATION app_pub FOR ALL TABLES;
-- hoặc
-- CREATE PUBLICATION app_pub FOR TABLE schema_a.*, schema_b.*;
Chuẩn bị destination
Tạo database trống, extensions cần thiết, role ứng dụng.
-- trên destination PG15
CREATE ROLE app_user LOGIN PASSWORD 'AppStrongPass!' NOINHERIT;
CREATE DATABASE app_db OWNER app_user;
\c app_db
-- cài extension như source
CREATE EXTENSION IF NOT EXISTS pgcrypto;
Cấu trúc schema có thể dump từ source:
pg_dump -s -d app_db_src | psql -d app_db_dest
Tạo subscription trên destination DB
-- trên PG15, vào đúng DB
\c app_db
CREATE SUBSCRIPTION app_sub
CONNECTION 'host=SRC_HOST port=5432 dbname=app_db user=repl_user password=StrongPass!'
PUBLICATION app_pub
WITH (copy_data = true, create_slot = true, enabled = true);
Giải thích nhanh
copy_data=true: để đồng bộ dữ liệu ban đầu.create_slot=true: sẽ tạo replication slot trên source để giữ WAL cho sub này.enabled=true: chạy ngay. Có thể đểfalserồi chạy copy chủ động nếu muốn kiểm soát.
Theo dõi sync ban đầu
-- trên destination DB
SELECT subname, status, received_lsn, last_msg_send_time, last_msg_receipt_time, latest_end_lsn
FROM pg_stat_subscription;
-- trên source DB
SELECT slot_name, active, restart_lsn FROM pg_replication_slots;
SELECT application_name, state, sent_lsn, replay_lag FROM pg_stat_replication;
Khi copy xong, subscription chuyển sang trạng thái streaming. Lag về gần 0 là tốt.
Đồng bộ các thay đổi không phải bảng
Logical replication không tự mang sequence, function, trigger, index partial đặc biệt, hay constraint mới. Cách làm an toàn:
Dump schema từ source sang destination định kỳ trước cutover:
pg_dump -s -d app_db_src | psql -d app_db_dest
Nếu có sequence, đồng bộ giá trị:
-- ví dụ
SELECT setval('schema_a.order_id_seq', (SELECT max(id) FROM schema_a.orders));
Kế hoạch Cutover
Trình tự Cutover
- Kích hoạt maintenance mode hoặc block writes tại application layer.
- Monitor replication lag, đảm bảo lag bằng 0 (đạt zero lag).
- Thực thi write-lock trên source database.
- Disable subscription để finalize state, sau đó thực hiện data validation (ví dụ:
row count,checksum). - Switch endpoint của PgBouncer sang destination DB.
- Enable writes trên destination database và disable maintenance mode tại application layer.
Chi tiết các bước Cutover
Bước 1: Kích hoạt Maintenance Mode
Ví dụ Rails:
ApplicationSetting.current.update(maintenance_mode: true, maintenance_mode_message: "DB cutover, vài phút thôi")
Hoặc block write requests ở routes. Đảm bảo các background jobs dừng ghi mới trong cutover window.
Bước 2: Monitor Replication Lag
-- destination DB
SELECT pg_sleep(1)
FROM generate_series(1,60)
WHERE (SELECT COALESCE(SUM(lag),0)
FROM (SELECT EXTRACT(EPOCH FROM (now() - last_msg_send_time)) AS lag
FROM pg_stat_subscription) t) = 0;
Bước 3: Thực thi Write-Lock trên Source DB
Tùy vào application. Phương án là revoke quyền write của role app_user trong cutover window:
-- trên source DB
REVOKE INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE INSERT, UPDATE, DELETE ON TABLES FROM app_user;
Hoặc dùng pg_terminate_backend với filter app_name để terminate các session ghi đang mở, nhưng cần thực hiện cẩn trọng.
Bước 4: Finalize State và Data Validation
Disable subscription trong 10 đến 20 giây để finalize data state.
-- destination DB
ALTER SUBSCRIPTION app_sub DISABLE;
Thực hiện validation số lượng record của các bảng quan trọng:
-- source DB
SELECT COUNT(*) FROM schema_a.orders WHERE created_at < now();
-- destination DB
SELECT COUNT(*) FROM schema_a.orders;
Có thể dùng checksum theo từng batch id hoặc theo cột updated_at. Với các bảng lớn, validate theo range của primary key.
Bước 5: Switch Endpoint PgBouncer
Chỉnh pgbouncer.ini trỏ sang destination host:
[databases]
app_db = host=DEST_HOST port=5433 dbname=app_db user=app_user password=AppStrongPass!
Reload config PgBouncer:
pgbouncer -R /etc/pgbouncer/pgbouncer.ini
Kiểm tra:
psql "host=pgbouncer port=6432 dbname=app_db user=app_user" -c "select version();"
Bước 6: Enable Writes và Disable Maintenance
-- trên source DB, cấp lại quyền nếu cần duy trì parallel operation (read-only)
GRANT INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
-- trên destination DB, đảm bảo full privileges
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_user;
Tại application:
ApplicationSetting.current.update(maintenance_mode: false)
Parallel Operation
Có thể bật reverse logical replication từ destination về source để hỗ trợ rollback plan. Thực tế tôi thấy cũng có thể giữ nguyên source instance ở chế độ read-only vài ngày trước khi cleanup.
Post-Cutover Verification
- Application read/write vận hành bình thường.
- Không có lỗi
unique violation. - SQL p95 latency không tăng đột ngột.
- Replication slot trên source instance có thể giữ thêm vài giờ rồi mới drop.
Cleanup
-- destination DB
DROP SUBSCRIPTION IF EXISTS app_sub;
-- source DB
SELECT pg_drop_replication_slot('app_sub');
Drop publication khi chắc chắn không còn sử dụng:
DROP PUBLICATION IF EXISTS app_pub;
Monitoring và Alerting
- Subscription lag (theo thời gian).
- WAL disk usage tăng đột biến.
- Lỗi của apply worker trên destination DB.
- Row count mismatch ở các bảng trọng yếu sau cutover.
Fallback/Rollback Plan
Phương án A (Application-level issue)
Switch PgBouncer endpoint về source DB và enable writes trên source DB.
Subscription vẫn còn thì dữ liệu mới từ source sẽ không tự động sync sang destination. Sau khi fix lỗi, có thể re-sync thủ công hoặc rebuild logical replication.
Phương án B (Critical destination DB issue)
Switch endpoint về source DB.
Drop subscription và slot để tránh WAL retention không cần thiết trên source DB.
Giữ lại destination instance để investigate.
Tối ưu Performance (Bulk Data Copy)
- Tăng
maintenance_work_mem,work_memtrên destination DB cho giai đoạn initial copy. - Tắt autovacuum cho các bảng đang copy và enable lại sau khi xong để tránh I/O contention.
- Tạo index sau khi initial copy hoàn tất nếu publication theo từng nhóm bảng.
- Giới hạn batch size để destination DB theo kịp.
Kết luận
Cho đến hiện tại thì với tôi tôi thấy logical replication là phương pháp ít rủi ro nhất để nâng cấp phiên bản Postgres hoặc migrate server mà không gây ra significant service disruption. Cái yêu cầu cốt lõi là chuẩn bị kỹ schema và permissions, thực hiện initial data copy, monitor lag và thực hiện cutover đúng thời điểm, có monitoring và fallback plan rõ ràng. Nếu anh em có phương án hay hơn tốt hơn tôi xin được lắng nghe học hỏi.



