لماذا التوافر العالي لـ PostgreSQL على VPS وليس RDS
يتعامل Amazon RDS Multi-AZ مع التحوّل بكفاءة، لكن نموذج تسعيره مصمَّم لتنمو الفاتورة بشكل غير خطي: التخزين، وIOPS المُخصَّصة، والاتصالات المتزامنة، وتكرار Multi-AZ كلها تُفوتَر بشكل منفصل. لخدمة SaaS ذات قاعدة بيانات متنامية، يتجاوز الفارق بسرعة تكلفة خادم VPS مخصَّص.
على ثلاثة خوادم VPS بصلاحية root، يوفّر Patroni الخدمة ذاتها: قائد واحد يقبل الكتابة، ونسختان متزامنتان أو غير متزامنتين تتبعانه، وetcd يحتفظ بالنصاب. إن سقط القائد، يكشف Patroni الغياب عبر اتفاقية إيجار etcd (TTL قابل للضبط، عادةً 30 ثانية) ويُرقّي النسخة الأحدث. لا تدخّل بشري، ولا تحديث DNS يدوي إن كان موزّع الأحمال مثل HAProxy يشير إلى نقطة نهاية صحة Patroni.
ما يقدّمه هذا الكلستر
- تحوّل تلقائي خلال 30 ثانية — يكشف Patroni فقدان القائد بانتهاء اتفاقية etcd ويُرقّي دون تدخّل.
- نسخ متزامن قابل للضبط —
synchronous_mode: trueيضمن عدم فقدان أي معاملة مؤكَّدة عند تعطّل القائد. - REST API مدمجة —
GET /leaderوGET /healthوPOST /switchover: استعلام الكلستر وإدارته دون عميل PostgreSQL. - تكلفة ثابتة وقابلة للتنبؤ — ثلاثة خوادم VPS بموارد محدّدة، بدون مفاجآت في الفاتورة.
- إضافات حرّة —
pg_hba.confوpostgresql.confوpgvectorوPostGIS: لا قيود تفرضها خدمة مُدارة. - نسخ احتياطي مركزي — يتكامل pgBackRest 2.59 بشكل أصلي مع Patroni لنسخ احتياطي تدريجي من النسخة المتماثلة.
معمارية الكلستر: ثلاث عُقد، نصاب واحد
يرتكز الكلستر على ثلاث طبقات:
etcd يحتفظ بسجل الإعداد الموزَّع (DCS). هو من يملك اتفاقية إيجار القائد. إن لم يجدّد قائد Patroni هذه الاتفاقية خلال مهلة TTL، أطلقها etcd وتنافس عليها العُقد الاحتياطية بالانتخاب. مع ثلاث عُقد etcd (واحدة لكل VPS)، يتحمّل النصابُ فقدانَ عُقدة واحدة دون خسارة التوافر.
Patroni يعمل على كل VPS جنبًا إلى جنب مع PostgreSQL. يتولّى تهيئة الكلستر، وإعداد postgresql.conf وpg_hba.conf، وتتبّع تأخّر النسخ، والتحوّل. يعرض REST API على المنفذ 8008.
PostgreSQL تديره Patroni بالكامل — لا تُعدّل postgresql.conf مباشرةً؛ كل تغيير يمر عبر patronictl edit-config ليبقى متزامنًا على العُقد الثلاث.
تدفّق النسخ: يستقبل القائد الكتابة على شكل WAL، وتتصل النسخ عبر pg_basebackup عند أول تشغيل ثم تتابع تدفّق WAL باستمرار. في الوضع المتزامن، ينتظر القائد تأكيد نسخة واحدة على الأقل قبل إرجاع COMMIT للعميل.
المتطلبات: الموارد والشبكة
كُتب هذا الدليل باستخدام Patroni 4.1.5 وetcd 3.6.6 وPostgreSQL 17 على Debian 12.
الحد الأدنى الموصى به من الموارد لكل عُقدة
- 2 vCPU / 4 جيجابايت RAM — كافٍ للبدء؛ خطّط لـ 8 جيجابايت RAM بمجرد تجاوز قاعدة البيانات بضعة جيجابايتات في
shared_buffers. - SSD NVMe — النسخ WAL حسّاس لزمن انتظار الكتابة؛ القرص الدوّار يُدهور تأخّر النسخ.
- شبكة خاصة بين العُقد الثلاث — يجب ألا يمر تواصل etcd وPatroni مع PostgreSQL عبر الإنترنت.
- IPv4 مخصَّصة — للوصول الخارجي للعملاء ولـ
pg_hba.confالخاص بالنسخ المتماثلة. - NTP مُزامَن (chrony) — انجراف < 1 ثانية — يرفض etcd النصابَ إن انجرفت ساعة عُقدة أكثر من ثانية. هذا الفخ الأكثر شيوعًا على خوادم VPS.
التثبيت: من الصفر إلى كلستر يعمل
مزامنة الساعة على العُقد الثلاث
على كل عُقدة، ثبّت chrony وفعّله قبل أي خطوة أخرى:
apt install -y chrony
systemctl enable --now chronyd
chronyc trackingتأكّد أن System time offset أقل من 0.1 ثانية. أي انجراف يتجاوز ثانية يتسبّب في انتهاء مهلة etcd وحلقات انتخاب لا تنتهي.
تثبيت etcd 3.6 على العُقد الثلاث
عرّف متغيرات البيئة الخاصة بكل عُقدة (استبدل NODE1_IP وNODE2_IP وNODE3_IP بعناوين IP الخاصة):
ETCD_VER=v3.6.6
curl -L https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz \
| tar xz -C /usr/local/bin --strip-components=1 etcd-${ETCD_VER}-linux-amd64/etcd \
etcd-${ETCD_VER}-linux-amd64/etcdctlأنشئ /etc/etcd/etcd.conf.yml على كل عُقدة (مثال لـ pg-node1):
name: pg-node1
data-dir: /var/lib/etcd
listen-peer-urls: http://NODE1_IP:2380
listen-client-urls: http://NODE1_IP:2379,http://127.0.0.1:2379
initial-advertise-peer-urls: http://NODE1_IP:2380
advertise-client-urls: http://NODE1_IP:2379
initial-cluster: pg-node1=http://NODE1_IP:2380,pg-node2=http://NODE2_IP:2380,pg-node3=http://NODE3_IP:2380
initial-cluster-token: pg-cluster-token
initial-cluster-state: newأنشئ وحدة systemd، فعّل etcd وشغّله على العُقد الثلاث قبل المتابعة.
التحقق من نصاب etcd
من أي عُقدة:
etcdctl --endpoints=http://NODE1_IP:2379,http://NODE2_IP:2379,http://NODE3_IP:2379 \
endpoint status --write-out=tableانتظر ظهور ثلاثة صفوف — أحدها يُظهر IS LEADER بقيمة true والآخران false — وأن يكون ERRORS فارغًا. إن غابت عُقدة، تحقّق من جدار الحماية على المنفذين 2379 و2380.
تثبيت PostgreSQL وPatroni
على العُقد الثلاث:
# PostgreSQL من مستودع PGDG الرسمي
apt install -y curl ca-certificates
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
echo "deb https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" > /etc/apt/sources.list.d/pgdg.list
apt update && apt install -y postgresql-17
# Patroni ومُشغّل etcd
pip3 install patroni[etcd3] psycopg2-binaryأوقف PostgreSQL — يتولّى Patroni تهيئة الكلستر:
systemctl stop postgresql
systemctl disable postgresqlإعداد Patroni على كل عُقدة
أنشئ /etc/patroni/patroni.yml (مثال لـ pg-node1):
scope: pg-cluster
namespace: /service/
name: pg-node1
restapi:
listen: NODE1_IP:8008
connect_address: NODE1_IP:8008
etcd3:
hosts: NODE1_IP:2379,NODE2_IP:2379,NODE3_IP:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
synchronous_mode: true
synchronous_node_count: 1
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
wal_keep_size: 128MB
max_wal_senders: 10
max_replication_slots: 10
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 0.0.0.0/0 scram-sha-256
- host all all 0.0.0.0/0 scram-sha-256
postgresql:
listen: NODE1_IP:5432
connect_address: NODE1_IP:5432
data_dir: /var/lib/postgresql/17/main
bin_dir: /usr/lib/postgresql/17/bin
authentication:
replication:
username: replicator
password: 'كلمة_مرور_النسخ'
superuser:
username: postgres
password: 'كلمة_مرور_postgres'عدّل NODE1_IP لكل عُقدة.
تشغيل Patroni وتهيئة الكلستر
أنشئ وحدة systemd في /etc/systemd/system/patroni.service:
[Unit]
Description=Patroni PostgreSQL HA
After=network.target etcd.service
Requires=etcd.service
[Service]
Type=simple
User=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni/patroni.yml
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.targetشغّل أوّلًا على pg-node1 (ستقوم هذه العُقدة بـ initdb وستصبح القائد)، ثم على العُقدتين الأخريين بفاصل بضع ثوانٍ:
systemctl daemon-reload
systemctl enable --now patroniتابع التهيئة:
patronictl -c /etc/patroni/patroni.yml listالتحقق من الحالة الأولية للكلستر
المخرجات المتوقعة بعد اكتمال التهيئة:
+ Cluster: pg-cluster (7234567890123456789) +---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+-----------+------------------+--------------+---------+----+-----------+
| pg-node1 | NODE1_IP:5432 | Leader | running | 1 | |
| pg-node2 | NODE2_IP:5432 | Sync Standby | running | 1 | 0 |
| pg-node3 | NODE3_IP:5432 | Replica | running | 1 | 0 |
+-----------+------------------+--------------+---------+----+-----------+تظهر pg-node2 بوصفها Sync Standby: كل معاملة مؤكَّدة على القائد مضمونة على هذه العُقدة قبل إرجاع COMMIT للعميل.
إعداد pg_hba.conf عبر Patroni
لا تُعدّل pg_hba.conf مباشرةً. استخدم patronictl edit-config لإضافة قواعد الوصول في قسم pg_hba — يُوزّع Patroni الإعداد على جميع العُقد ويُعيد تحميل PostgreSQL تلقائيًا:
patronictl -c /etc/patroni/patroni.yml edit-configأضف قواعدك في كتلة YAML الخاصة بـ pg_hba. على VPS، host all all 0.0.0.0/0 scram-sha-256 هي نقطة البداية التي يمكن تضييقها وفق شبكتك الخاصة.
التحوّل التلقائي والتحوّل المُخطَّط: عرض تطبيقي
تحوّل مُحاكى — إيقاف مفاجئ للقائد.
قبل الإيقاف، سجّل حالة الكلستر:
patronictl -c /etc/patroni/patroni.yml list
# → pg-node1 هو Leader، pg-node2 هو Sync Standbyأوقف Patroni على القائد:
systemctl stop patroni # على pg-node1تابع الترقية على أحد العُقد الاحتياطية:
patronictl -c /etc/patroni/patroni.yml list
# → (بعد 10 إلى 30 ثانية)
# pg-node2 : Leader | running | TL 2
# pg-node3 : Replica | running | TL 2 | 0 MB
# pg-node1 : stoppedينتظر Patroni انتهاء اتفاقية إيجار etcd (TTL = 30 ث)، ثم تحصل pg-node2 (النسخة المتزامنة) على الاتفاقية وتُرقّي نفسها. التأخّر الفعلي بين 10 و30 ثانية تبعًا لقيمة loop_wait.
تحوّل مُخطَّط — تبديل بدون انقطاع.
للصيانة المجدوَلة، فضّل switchover الذي ينتظر حتى تلحق العُقدة المستهدفة بالقائد قبل التبديل:
patronictl -c /etc/patroni/patroni.yml switchover pg-cluster \
--master pg-node1 --candidate pg-node2ينتظر Patroni حتى يصبح التأخّر صفرًا، يُشير إلى pg-node2 بالترقية، ثم تُعيد pg-node1 الاتصال كنسخة متماثلة. المدة الفعلية: أقل من 5 ثوانٍ في الظروف الطبيعية.
REST API للحالة.
بدون عميل PostgreSQL، استعلم الحالة من موزّع الأحمال أو سكريبت المراقبة:
curl -s http://NODE1_IP:8008/leader # 200 = هذه هي العُقدة القائدة
curl -s http://NODE2_IP:8008/replica # 200 = نسخة متماثلة سليمة
curl -s http://NODE1_IP:8008/health # JSON: state, role, lagيمكن لـ HAProxy توجيه فحوصات الصحة نحو /leader و/replica لتوجيه الكتابة والقراءة على العُقد الصحيحة. راجع دليل HAProxy على VPS للتوصيل الكامل.
العمليات اليومية
النسخ الاحتياطي مع pgBackRest 2.59.
ثبّت pgBackRest على العُقد الثلاث وحدّد مستودعًا مشتركًا (تخزين كائنات S3، أو NFS، أو مجلد محلي مخصَّص). يسحب الإعداد الموصى به النسخ الاحتياطية من النسخة المتماثلة لتفادي تحميل القائد:
pgbackrest --stanza=pg-cluster --type=full backupفعّل الضغط والنسخ الاحتياطي التدريجي اليومي في pgbackrest.conf (repo1-retention-full=7). راجع دليل النسخ الاحتياطي على VPS للاستراتيجيات التكميلية.
مراقبة الكلستر.
patronictl list يُظهر التأخّر بالميجابايت لكل نسخة متماثلة. أنشئ تنبيهًا عند تجاوز التأخّر عتبة محدّدة (مثلًا 50 ميجابايت): يشير ذلك إلى نسخة بطيئة أو مشكلة شبكة. تُعيد نقطة النهاية GET /patroni بيانات JSON كاملة تشمل xlog_location وreplication_state.
التوسّع العمودي.
لزيادة موارد عُقدة: أوقف Patroni عليها (تتحوّل إلى نسخة منفصلة)، أعد تحجيم الـ VPS، وأعد التشغيل. تعيد Patroni الاتصال وتلحق بالتأخّر تلقائيًا عبر pg_rewind أو pg_basebackup حسب حجم الفجوة.
مقايضة synchronous_commit.
مع synchronous_mode: true، ينتظر كل COMMIT تأكيد العُقدة المتزامنة. على شبكة خاصة محلية، يتراوح التأخّر المُضاف بين 0.2 و1 ميلي ثانية. على شبكة أوسع (عُقد في مراكز بيانات مختلفة)، قد يؤثّر هذا التأخّر في التطبيقات كثيفة الكتابة. في هذه الحالة، انتقل إلى synchronous_mode: false مع النسخ غير المتزامن: ستفقد ضمان عدم فقدان البيانات عند تعطّل القائد، لكن الكتابة ستبقى سريعة. هذه مقايضة يجب توثيقها صراحةً في إعداداتك.
التصليب: TLS متبادل بين العُقد
بشكل افتراضي، يسير تواصل etcd واتصالات النسخ لـ PostgreSQL بدون تشفير عبر الشبكة الخاصة. على شبكة مشتركة أو بيئة متعددة المستأجرين، فعّل TLS المتبادل.
لـ etcd، أنشئ CA وشهادات لكل عُقدة، ثم أضف في etcd.conf.yml:
client-transport-security:
cert-file: /etc/etcd/tls/server.crt
key-file: /etc/etcd/tls/server.key
trusted-ca-file: /etc/etcd/tls/ca.crt
client-cert-auth: true
peer-transport-security:
cert-file: /etc/etcd/tls/peer.crt
key-file: /etc/etcd/tls/peer.key
trusted-ca-file: /etc/etcd/tls/ca.crt
peer-client-cert-auth: trueلنسخ PostgreSQL، استخدم sslmode=verify-full في معاملات اتصال primary_conninfo الخاصة بـ Patroni. تتحقق كل نسخة متماثلة بذلك من شهادة القائد.
استكشاف الأخطاء: أكثر خمسة أخطاء شيوعًا
1. فقدان نصاب etcd — الكلستر يرفض انتخاب قائد.
العَرَض: يُظهر patronictl list جميع العُقد بحالة running لكن بلا Leader. السبب: عُقدة etcd غير قابلة للوصول والنصاب (2 من 3) لم يعد مكتملًا. تحقق بـ etcdctl endpoint status — ستظهر العُقدة المعطوبة بدون استجابة أو بخطأ اتصال. أصلح العُقدة أو أزلها مؤقتًا من الكلستر (etcdctl member remove).
2. انجراف NTP — حلقة انتخابات.
العَرَض: يتغير القائد كل 30 ثانية، وتُظهر سجلات Patroni failed to update leader key. السبب: ساعة إحدى العُقد تنجرف بأكثر من ثانية. تحقق بـ chronyc tracking على كل عُقدة وأصلح قبل إعادة تشغيل Patroni.
3. احتمال تشقّق الدماغ — pg_rewind يرفض التطبيق.
العَرَض: قائد سابق يُعاد تشغيله وPatroni يرفض إعادة دمجه كنسخة متماثلة مع الخطأ could not connect to the target server: pg_rewind target server must be in standby mode. استمر الخادم في الكتابة بعد فقدان الاتفاقية. الحل: pg_rewind --target-pgdata=/var/lib/postgresql/17/main --source-server='host=NEW_LEADER_IP ...'، ثم أعد تشغيل Patroni.
4. رفض اتصال النظير — إدخال مفقود في pg_hba.conf.
العَرَض: تُهيَّأ النسخ لكنها تفشل مع FATAL: no pg_hba.conf entry for replication connection. القاعدة host replication replicator 0.0.0.0/0 scram-sha-256 غائبة من قسم pg_hba في patroni.yml. أضفها عبر patronictl edit-config — ليس مباشرةً في pg_hba.conf.
5. تأخّر متواصل بعد التحوّل — max_wal_senders غير كافٍ.
العَرَض: تُظهر النسخة تأخّرًا لا يتناقص بعد الترقية. السبب الشائع: max_wal_senders منخفض للغاية (القيمة الافتراضية 10 في بعض الإصدارات) وفتحة النسخ مكتظة. ارفعه إلى 20 عبر patronictl edit-config (المعامل max_wal_senders) وأعد التحميل.
كلستر يُدار ولا يُرتجَل
يغطي Patroni 4.1 مع etcd 3.6 معظم ما تقدّمه الخدمة المُدارة على صعيد التوافر: انتخاب تلقائي، نسخ متزامن، API للإدارة. الفرق هو التحكّم: صلاحية root، إضافات حرّة، تكلفة ثابتة، والقدرة على تشخيص العُقدة المتعثّرة بدلًا من انتظار دعم طرف ثالث.
المتطلب التشغيلي ليس تعقيد Patroni — الإجراء أعلاه يُظهر أنه قابل للإدارة. إنه الانضباط على ثلاثة محاور: NTP مُزامَن، نسخ احتياطية تُتحقَّق منها بانتظام، وكتيّب تشغيل للتحوّل يُختبَر قبل وقوع العطل.
للبدء، يحتاج كلستر Patroni بثلاث عُقد إلى ثلاثة خوادم VPS بصلاحية root وIPv4 مخصَّصة وشبكة خاصة. راجع دليل التثبيت الأساسي PostgreSQL على VPS والمقارنة الاستضافة الذاتية مقابل Amazon RDS لاختيار النهج المناسب لحِملك.