Replication و Streaming در PostgreSQL
Replication یعنی نگهداری چند نسخه از دیتابیس روی سرورهای مختلف. PostgreSQL دو نوع replication دارد: Physical (Streaming) برای کپی کامل و Logical برای کپی انتخابی. در این فصل با هر دو، بهعلاوه HA با Patroni و pgpool آشنا میشویم.
Replication یعنی نگهداری چند نسخه از دیتابیس روی سرورهای مختلف. PostgreSQL دو نوع replication دارد: Physical (Streaming) برای کپی کامل و Logical برای کپی انتخابی. در این فصل با هر دو، بهعلاوه HA با Patroni و pgpool آشنا میشویم.
چرا Replication؟
- High Availability: اگر primary سقوط کند، standby ادامه میدهد
- Load Balancing: read queryها روی replicaها
- Disaster Recovery: کپی در data center دیگر
- Geographical distribution: replica نزدیک کاربر
- Reporting: query سنگین روی replica، بدون تاثیر روی primary
انواع Replication
| ویژگی | Physical (Streaming) | Logical |
|---|---|---|
| سطح | byte-level (WAL) | row-level |
| کپی | کل cluster | جدولهای انتخابی |
| نسخه PG | باید یکسان | میتواند متفاوت |
| standby مینویسد؟ | خیر (read-only) | بله |
| کاربرد | HA، DR، read scaling | upgrade، sharding، sync انتخابی |
Streaming Replication – راهاندازی
روی Primary
# postgresql.conf
listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
wal_keep_size = 1GB # یا max_wal_size در PG 13+
hot_standby = on
# pg_hba.conf - اجازه اتصال replication از standby
host replication replicator 10.0.0.20/32 scram-sha-256
-- ساخت user مخصوص replication
CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'StrongPass!';
روی Standby
# متوقف کردن standby
sudo systemctl stop postgresql
# پاک کردن data directory
sudo rm -rf /var/lib/postgresql/16/main/*
# گرفتن base backup از primary
sudo -u postgres pg_basebackup
-h 10.0.0.10
-U replicator
-D /var/lib/postgresql/16/main
-P -v -R # -R = standby.signal و primary_conninfo بسازد
# -R خودکار این فایلها را میسازد:
# /var/lib/postgresql/16/main/standby.signal (نشانه standby بودن)
# و در postgresql.auto.conf:
# primary_conninfo = 'host=10.0.0.10 user=replicator password=...'
# شروع
sudo systemctl start postgresql
تست
-- روی Primary
SELECT * FROM pg_stat_replication;
-- application_name | client_addr | state | sent_lsn | replay_lsn | replay_lag
-- walreceiver | 10.0.0.20 | streaming | 0/3000148 | 0/3000148 | 00:00:00
-- روی Standby
SELECT pg_is_in_recovery(); -- t (true)
-- query کار میکند:
SELECT count(*) FROM products;
-- اما نمیتوان نوشت:
INSERT INTO products ...;
-- ERROR: cannot execute INSERT in a read-only transaction
Synchronous vs Asynchronous
Asynchronous (پیشفرض)
primary منتظر standby نیست. سریعتر، اما ریسک از دست دادن داده در failure.
Synchronous
# postgresql.conf روی primary
synchronous_commit = on
synchronous_standby_names = 'standby1, standby2'
# یا برای any one:
synchronous_standby_names = 'ANY 1 (standby1, standby2)'
هر COMMIT منتظر میماند تا standby دریافت کند → durability بالاتر، latency بیشتر.
Cascading Replication
Primary
│
↓
Standby 1
│
↓
Standby 2 (replicate from Standby 1)
مفید برای کاهش بار روی primary وقتی چند standby داریم.
Logical Replication
کپی row-level بر اساس PUBLICATION/SUBSCRIPTION:
روی Source (Publisher)
# postgresql.conf
wal_level = logical
max_wal_senders = 10
max_replication_slots = 10
-- ساخت publication
CREATE PUBLICATION my_pub FOR TABLE products, orders;
-- یا همه جدولها
CREATE PUBLICATION my_pub_all FOR ALL TABLES;
-- یا فقط INSERT و UPDATE
CREATE PUBLICATION my_pub_iu FOR TABLE products
WITH (publish = 'insert, update');
-- لیست
SELECT * FROM pg_publication;
روی Target (Subscriber)
-- جدولها باید قبلاً وجود داشته باشند با همان schema
CREATE TABLE products (...);
CREATE TABLE orders (...);
-- ساخت subscription
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=10.0.0.10 dbname=shopdb user=replicator password=...'
PUBLICATION my_pub;
-- داده اولیه کپی میشود، سپس تغییرات stream میشوند
-- بررسی
SELECT * FROM pg_stat_subscription;
کاربردهای Logical Replication
- upgrade: PG 14 → PG 16 با downtime حداقل
- cross-version: replication بین نسخههای مختلف
- partial sync: فقط جدولهای خاص
- combine sources: داده از چند DB در یک DB جمع شود
- ETL: انتقال داده real-time به analytical DB
Failover – تعویض اضطراری
وقتی primary سقوط میکند، باید standby را به primary تبدیل کنیم:
# روی standby - تبدیل به primary
pg_ctl promote -D /var/lib/postgresql/16/main
# یا با SQL (PG 12+)
psql -c "SELECT pg_promote();"
# حالا standby میتواند بنویسد
# باید app را به IP جدید point کنید
چالشهای Failover دستی
- تشخیص زمان دقیق failure
- اطمینان از sync بودن standby
- تغییر connection string در app
- تبدیل primary سابق به standby
راهحل: Patroni یا ابزارهای HA دیگر.
Patroni – HA خودکار
Patroni با etcd/Consul/ZooKeeper برای coordination، failover خودکار را مدیریت میکند.
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Patroni │ │ Patroni │ │ Patroni │
│ Primary │ │ Standby │ │ Standby │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└────────────────┼────────────────┘
│
┌───────▼────────┐
│ etcd / Consul │
│ (coordination) │
└────────────────┘
نصب Patroni
sudo apt install patroni etcd
# /etc/patroni/config.yml
scope: pgcluster
namespace: /service/
name: node1
restapi:
listen: 0.0.0.0:8008
connect_address: 10.0.0.10:8008
etcd:
hosts: 10.0.0.30:2379,10.0.0.31:2379,10.0.0.32:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 10
max_replication_slots: 10
postgresql:
listen: 0.0.0.0:5432
connect_address: 10.0.0.10:5432
data_dir: /var/lib/postgresql/16/main
authentication:
superuser:
username: postgres
password: secret
replication:
username: replicator
password: replpass
قابلیتهای Patroni
- failover خودکار
- switchover (تعویض با برنامه)
- اضافه/کم کردن node خودکار
- REST API برای monitoring
- integration با HAProxy/pgBouncer
pgpool-II – Connection Routing
pgpool-II این قابلیتها را دارد:
- Connection pooling
- Load balancing (write به primary، read به standbyها)
- Auto failover detection
- Query caching
sudo apt install pgpool2
# pgpool.conf
backend_hostname0 = '10.0.0.10'
backend_port0 = 5432
backend_weight0 = 1
backend_flag0 = 'ALLOW_TO_FAILOVER'
backend_hostname1 = '10.0.0.20'
backend_port1 = 5432
backend_weight1 = 1
backend_flag1 = 'ALLOW_TO_FAILOVER'
load_balance_mode = on
master_slave_mode = on
master_slave_sub_mode = 'stream'
monitoring Replication
-- روی Primary
SELECT
application_name, client_addr, state,
pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) AS lag,
sync_state
FROM pg_stat_replication;
-- روی Standby - تأخیر replication
SELECT
pg_is_in_recovery(),
pg_last_wal_receive_lsn(),
pg_last_wal_replay_lsn(),
EXTRACT(EPOCH FROM (NOW() - pg_last_xact_replay_timestamp())) AS lag_seconds;
-- alerting بر اساس lag > 60s
-- در Prometheus + Grafana
Replication Slots
slot تضمین میکند WAL تا زمانی که standby آن را دریافت نکرده، حذف نشود:
-- ساخت slot برای standby
SELECT pg_create_physical_replication_slot('standby1_slot');
-- در standby، primary_conninfo:
-- primary_slot_name = 'standby1_slot'
-- لیست slots
SELECT slot_name, active, restart_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag
FROM pg_replication_slots;
-- هشدار: slot غیرفعال میتواند باعث پر شدن دیسک شود!
-- اگر standby آفلاین برای مدت طولانی، slot را حذف کنید:
SELECT pg_drop_replication_slot('standby1_slot');
بهترین شیوهها
- برای HA: حداقل ۳ node با Patroni
- standby در data center جداگانه برای DR
- monitoring lag (alerting برای lag > 30s)
- replication slot با احتیاط (میتواند disk را پر کند)
- synchronous replication فقط اگر latency اجازه میدهد
- test کردن failover بهصورت دورهای
- backup هم داشته باشید (replication برای backup نیست!)
جمعبندی
- Streaming Replication: کپی WAL، کل cluster
- Logical Replication: row-level، انتخابی، cross-version
- Synchronous برای durability، Asynchronous برای performance
- Patroni برای failover خودکار
- pgpool-II برای routing و load balancing
- Replication slots برای حفظ WAL
- monitoring replication lag حیاتی است