---
title: 'Postgres 18 ترقية بدون توقف: pg_createsubscriber'
description: ترقية Postgres 17 إلى 18.3 بدون توقف باستخدام pg_createsubscriber والنسخ المتماثل المنطقي. Docker شرح مع sequences, cutover, rollback.
url: https://nerdleveltech.com/ar/postgres-18-zero-downtime-upgrade-pg-createsubscriber-tutorial
language: ar
date: '2026-05-13'
category: backend
tags:
  - postgres
  - postgresql
  - logical-replication
  - pg_createsubscriber
  - zero-downtime
  - database
  - backend
  - architecture
  - docker
markdown_url: https://nerdleveltech.com/ar/postgres-18-zero-downtime-upgrade-pg-createsubscriber-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/postgres-18-zero-downtime-upgrade-pg-createsubscriber-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# Postgres 18 ترقية بدون توقف: pg_createsubscriber

لترقية Postgres 17 إلى 18 بدون توقف، قم بتشغيل `pg_createsubscriber` على نسخة احتياطية فيزيائية (physical standby) لتحويلها إلى مشترك منطقي (logical subscriber)، ثم قم بـ `pg_upgrade` لهذا المشترك إلى Postgres 18.3 بينما يستمر الأساسي القديم في استقبال عمليات الكتابة. يقوم النسخ المتماثل المنطقي (Logical replication) بتحديث العنقود (cluster) الجديد، وتقوم بمزامنة المتسلسلات (sequences) يدويًا، وينتقل تدفق البيانات (traffic) في ثوانٍ بدلاً من دقائق.[^1][^2]

مسار `pg_upgrade` الكلاسيكي سريع — عادةً بضع دقائق لعنقود صغير — ولكنه لا يزال يتطلب **إغلاق** العنقود القديم أثناء تشغيله.[^3] بالنسبة لتطبيق يرسل تنبيهات للدعم الفني عند أول انقطاع لمدة 30 ثانية، فإن "بضع دقائق" ليست صفرًا. تجعل نسخة PostgreSQL 18.3 (الصادرة في 26 فبراير 2026)[^4] مسار "وقت التوقف الصفري" أسهل بكثير مما كان عليه سابقًا من خلال تحسين `pg_createsubscriber`، وهو الملف الثنائي الذي يحول النسخة الاحتياطية الفيزيائية المتدفقة إلى مشترك منطقي في خطوة واحدة. يستعرض هذا البرنامج التعليمي التدفق الكامل باستخدام Docker Compose، لينتهي بعنقود Postgres 18.3 بدأ حياته كعنقود Postgres 17.9 أساسي.

## ملخص

ستقوم بتشغيل Postgres 17.9 أساسي في Docker، وربط نسخة احتياطية فيزيائية متدفقة، وتحويل تلك النسخة الاحتياطية إلى مشترك منطقي باستخدام `pg_createsubscriber --all` (وهو علم "flag" جديد في PG 18[^5])، ثم `pg_upgrade` إلى Postgres 18.3، ومزامنة المتسلسلات، وتحويل تدفق البيانات. إجمالي وقت التوقف عن الكتابة: تبديل اتصال واحد، عادةً في أقل من ثانية واحدة. تم التحقق من كل أمر وإصدار في هذا البرنامج التعليمي مقابل وثائق `postgresql.org` و Docker Hub في 13 مايو 2026.

## ما ستتعلمه

- لماذا لا يمكن لـ `pg_upgrade` بمفرده توفير وقت توقف صفري، وما الذي يغيره الجمع بين `pg_createsubscriber` + `pg_upgrade`.
- كيفية تكوين Postgres 17.9 للنسخ المتماثل المنطقي وإضافة نسخة احتياطية فيزيائية متدفقة في Docker.
- كيفية تشغيل `pg_createsubscriber --all` لتحويل تلك النسخة الاحتياطية إلى مشترك منطقي، بما في ذلك متطلبات GUC الأساسية.
- كيفية إجراء `pg_upgrade` للمشترك الجديد إلى Postgres 18.3 بينما يستمر الناشر (publisher) في خدمة عمليات الكتابة.
- كيفية التعامل مع المتسلسلات (sequences) — وهي جزء الحالة الوحيد الذي **لا** ينقله النسخ المتماثل المنطقي — دون التصادم مع معرفات (IDs) الناشر.
- كيفية التحقق من اكتمال النسخ المتماثل عبر `pg_subscription_rel`، ثم إجراء عملية التحويل (cutover).
- خمسة أنماط فشل شائعة وخطة تراجع (rollback) نظيفة.

## المتطلبات الأساسية

- Docker 27.0 أو أحدث مع Compose v2 (مدمج في حزم Docker Desktop و Docker Engine الحديثة).[^6]
- صدفة POSIX (bash أو zsh).
- الإلمام بمصطلحات SQL ونسخ Postgres المتماثل (الناشر، المشترك، فتحة النسخ المتماثل).
- منافذ TCP شاغرة 5532 و 5533 و 5534 على `localhost`. تم إزاحة المنافذ عمدًا عن 5432 لتجنب التصادم مع نسخة Postgres الموجودة لدى المطور.
- حوالي 2 جيجابايت من مساحة القرص الحرة لمجلدات Docker المستخدمة في هذا الشرح.

## لماذا يترك pg\_upgrade وحده فجوة في وقت التوقف

يقوم `pg_upgrade` بإعادة كتابة كتالوجات النظام و (باستخدام `--link`) بربط ملفات البيانات في العنقود الجديد، وهذا هو سبب سرعته.[^3] ولكن يجب إيقاف العنقود القديم قبل تشغيل `pg_upgrade`، ولا يتم بدء تشغيل العنقود الجديد حتى ينتهي `pg_upgrade`. خلال تلك النافذة — دقائق لقاعدة بيانات صغيرة، وأطول بكثير لقاعدة كبيرة — يتم رفض كل عملية كتابة. أضف إلى ذلك الوقت المستغرق لتفريغ الاتصالات، وتشغيل analyze على العنقود الجديد، وتسخين الذاكرة المؤقتة (caches)، وغالبًا ما تتحول "بضع دقائق" إلى نافذة صيانة حقيقية.

يتجنب النسخ المتماثل المنطقي هذه المشكلة لأن **الناشر والمشترك قد يشغلان إصدارات رئيسية مختلفة** في نفس الوقت.[^7] تقوم ببناء نسخة Postgres 18.3 تتبع نسخة Postgres 17.9 الأساسية، وتنتظر حتى تلحق بها، ثم تقوم بالتحويل. وقت التوقف الوحيد هو تبديل الاتصال.

## سير عمل pg\_createsubscriber و pg\_upgrade

قبل إصدار PG 17، كانت عملية الترقية بدون وقت توقف تتضمن `pg_dump --schema-only`، وعبارات `CREATE PUBLICATION` و `CREATE SUBSCRIPTION` يدوية، وانتظار انتهاء عملية COPY الأولية — وهي عملية تستغرق ساعات في قاعدة بيانات بحجم عدة تيرابايت لأن كل صف كان يجب إعادة نسخه عبر الشبكة. قدم PostgreSQL 17 أداة `pg_createsubscriber` لتخطي خطوة النسخ تلك تمامًا: فهي تأخذ نسخة احتياطية **فيزيائية** متدفقة موجودة (وهي بالفعل نسخة مطابقة للأصل بايت ببايت من الأساسي) وتحولها إلى مشترك **منطقي** عن طريق إيقافها، وتسجيل LSN، وإنشاء المنشورات والاشتراكات، وإعادة لف موضع التطبيق إلى ذلك الLSN.[^8] لا يقوم الملف الثنائي بنسخ أي بيانات؛ بل يقوم فقط بتبديل وضع النسخ المتماثل.

أضاف PostgreSQL 18 ثلاثة أعلام (flags) مفيدة فوق ذلك: `--all` (للتعامل مع كل قاعدة بيانات مستخدم في استدعاء واحد)، `--clean` (لإسقاط المنشورات المنقولة من تشغيل سابق)، و `--enable-two-phase` (لتفعيل دعم 2PC).[^5] علم `--all` هو الأهم بالنسبة للترقيات، لأن عناقيد الإنتاج نادرًا ما تحتوي على قاعدة بيانات واحدة.

اجمع بين `pg_createsubscriber` و `pg_upgrade` وسيصبح المسار كالتالي:

1. إنشاء نسخة احتياطية فيزيائية متدفقة من نسخة PG 17 الأساسية الحالية.
2. إيقاف النسخة الاحتياطية بشكل نظيف.
3. تشغيل `pg_createsubscriber --all` مقابل دليل بيانات النسخة الاحتياطية باستخدام ملفات PG 17 الثنائية.
4. تشغيل `pg_upgrade` على دليل بيانات النسخة الاحتياطية للتحويل من PG 17 ← PG 18.3.
5. بدء تشغيل مثيل PG 18.3 الجديد؛ يستأنف النسخ المتماثل المنطقي من الLSN المسجل ويلحق بكل ما قبله الناشر أثناء تشغيل الترقية.
6. مزامنة المتسلسلات، تحويل تدفق البيانات، وإسقاط المنشورات على الأساسي القديم.

## الخطوة 1: تشغيل ناشر Postgres 17.9

قم بإنشاء دليل للمشروع وضع فيه ملف `Docker-compose.yml`:

```yaml
# Docker-compose.yml
name: pg18-zdt
services:
  publisher:
    image: postgres:17.9
    container_name: pg17-publisher
    ports:
      - "5532:5432"
    environment:
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: shop
    volumes:
      - publisher_data:/var/lib/postgresql/data
    command:
      - "postgres"
      - "-c"
      - "wal_level=logical"
      - "-c"
      - "max_wal_senders=10"
      - "-c"
      - "max_replication_slots=10"
      - "-c"
      - "listen_addresses=*"

volumes:
  publisher_data:
```

هناك إعدادان مهمان:

- `wal_level=logical` يكتب المعلومات الإضافية التي يحتاجها فك التشفير المنطقي (logical decoder).[^9] في Postgres 18 يمكنك ترك `wal_level` المكون عند `replica` وسيقوم المستوى *الفعلي* بالترقية التلقائية بمجرد إنشاء أول فتحة منطقية؛[^10] بالنسبة لـ PG 17 لا تزال بحاجة لضبطه صراحة وإعادة التشغيل، ولهذا السبب قمنا بكتابته هنا بشكل ثابت.
- `max_wal_senders=10` و `max_replication_slots=10` هي القيم الافتراضية ولكن من الجيد التصريح عنها. تحتاج `pg_createsubscriber` إلى فتحة واحدة على الأقل لكل قاعدة بيانات، بالإضافة إلى مساحة إضافية لأي نسخ احتياطية فيزيائية موجودة.[^11]

قم بتشغيله وبذر مجموعة بيانات صغيرة:

```bash
Docker compose up -d publisher
Docker exec -i pg17-publisher psql -U postgres -d shop <<'SQL'
CREATE TABLE products (
  id        bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  sku       text NOT NULL UNIQUE,
  price_eur numeric(10,2) NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO products (sku, price_eur) VALUES
  ('NRD-001', 19.99),
  ('NRD-002', 29.50),
  ('NRD-003', 9.00);

CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'replpw';
SQL
```

افتح `pg_hba.conf` للسماح باتصال النسخ الاحتياطي (replication) من الخادم الاحتياطي (standby) ثم أعد التحميل:

```bash
Docker exec pg17-publisher bash -c "echo 'host replication replicator 0.0.0.0/0 scram-sha-256' >> /var/lib/postgresql/data/pg_hba.conf"
Docker exec pg17-publisher psql -U postgres -c "SELECT pg_reload_conf();"
```

في بيئة الإنتاج، يجب عليك تحديد نطاق CIDR هذا ليكون العنوان الفعلي للخادم الاحتياطي. بالنسبة لعرض تجريبي على localhost، فإن استخدام `0.0.0.0/0` مع كلمة مرور SCRAM يعد أمرًا جيدًا.

## الخطوة 2: إضافة خادم احتياطي فيزيائي متدفق (streaming physical standby)

قم بتشغيل `pg_basebackup` من حاوية PG 17.9 جديدة إلى وحدة تخزين (volume) Docker مسماة، ثم ابدأ تشغيل نسخة ثانية من Postgres على وحدة التخزين تلك:

```bash
Docker volume create pg18-zdt_standby_data
Docker run --rm --network pg18-zdt_default \
  -v pg18-zdt_standby_data:/var/lib/postgresql/data \
  -e PGPASSWORD=replpw \
  postgres:17.9 \
  pg_basebackup -h publisher -p 5432 -U replicator \
    -D /var/lib/postgresql/data \
    -Fp -R -X stream -P
```

تقوم هذه الأعلام (flags) بمهام هامة:

- `-Fp` يكتب شجرة أدلة بتنسيق عادي (مطلوب بواسطة `pg_createsubscriber`، الذي يعمل على `PGDATA` حقيقي).[^12]
- `-R` يكتب `standby.signal` وسطر `primary_conninfo` في `postgresql.auto.conf`، بحيث يظهر المستلم كخادم احتياطي متدفق دون أي إعدادات إضافية.[^12]
- `-X stream` يقوم ببث WAL جنبًا إلى جنب مع النسخة الاحتياطية الأساسية بحيث يكون دليل البيانات متسقًا في اللحظة التي ينتهي فيها الأمر.

الآن ابدأ تشغيل الخادم الاحتياطي على المنفذ الخاص به:

```yaml
  standby:
    image: postgres:17.9
    container_name: pg17-standby
    ports:
      - "5533:5432"
    volumes:
      - standby_data:/var/lib/postgresql/data
    command: ["postgres", "-c", "listen_addresses=*"]

volumes:
  publisher_data:
  standby_data:
    external: true
    name: pg18-zdt_standby_data
```

أضف خدمة `standby` إلى ملف `Docker-compose.yml` ومدخل وحدة التخزين `standby_data`، ثم قم بتشغيل `Docker compose up -d standby`. تأكد من أن النسخ الاحتياطي المتدفق يعمل بشكل سليم من الناشر:

```bash
Docker exec pg17-publisher psql -U postgres -c \
  "SELECT application_name, state, sync_state FROM pg_stat_replication;"
```

يجب أن ترى صفًا واحدًا في حالة `streaming`. أدخل صفًا في الناشر واقرأه مرة أخرى من المنفذ 5533 للتأكد من أن إعادة التشغيل (replay) تعمل مباشرة.

## الخطوة 3: تحويل الخادم الاحتياطي باستخدام pg\_createsubscriber --all

يتطلب `pg_createsubscriber` أن يكون الخادم المستهدف **متوقفًا** قبل تشغيله.[^1] أوقف الخادم الاحتياطي بشكل نظيف، ثم استدعِ الملف الثنائي باستخدام حاوية PG 17.9 تعمل لمرة واحدة وتقوم بتركيب نفس وحدة تخزين البيانات:

```bash
Docker compose stop standby

Docker run --rm --network pg18-zdt_default \
  -v pg18-zdt_standby_data:/var/lib/postgresql/data \
  -e PGPASSWORD=dev \
  postgres:17.9 \
  pg_createsubscriber --all \
    -D /var/lib/postgresql/data \
    -P "host=publisher port=5432 user=postgres dbname=postgres" \
    -p 5432 \
    --verbose
```

يخبر العلم `--all` الملف الثنائي باكتشاف كل قاعدة بيانات مستخدم على الخادم الاحتياطي وإنشاء زوج من المنشور/الاشتراك (publication/subscription) لكل واحدة منها — وهي ميزة الراحة الجديدة في PG 18 التي تحل محل الإدراج اليدوي لكل قاعدة بيانات باستخدام `-d`.[^5] تتبع أسماء الفتحات (slots) اتفاقية التسمية الموثقة `pg_createsubscriber_<DB_OID>_<random_int>` حتى لا تتعارض مع أي فتحات موجودة لديك بالفعل.[^13]

خلف الكواليس، يقوم الملف الثنائي بستة أشياء:[^1]

1. يتحقق من المتطلبات الأساسية: يجب أن يكون لدى المصدر `wal_level = logical`، وأن تكون قيم `max_replication_slots` و `max_wal_senders` عالية بما يكفي، ويجب أن يكون لدى الهدف قيم `max_active_replication_origins` و `max_logical_replication_workers` و `max_worker_processes` عالية بما يكفي.
2. ينشئ منشورًا `FOR ALL TABLES` على المصدر لكل قاعدة بيانات.
3. ينشئ فتحة نسخ منطقي (logical replication slot) على المصدر لكل قاعدة بيانات.
4. يكتب معلمات الاسترداد (recovery parameters) في دليل بيانات الهدف مع ضبط `recovery_target_lsn` و `recovery_target_action = promote`.
5. يعيد تشغيل الهدف لفترة وجيزة حتى يقوم بإعادة التشغيل حتى ذلك الـ LSN ويتم ترقيته.
6. ينشئ الاشتراك المطابق على الهدف — دون نسخ البيانات الأولية، لأن النسخة الفيزيائية تمتلكها بالفعل.

عندما ينتهي الملف الثنائي، أعد تشغيل حاوية الخادم الاحتياطي:

```bash
Docker compose start standby
Docker exec pg17-standby psql -U postgres -d shop \
  -c "SELECT subname, subenabled FROM pg_subscription;"
```

يجب أن ترى `pg_createsubscriber_<oid>_<rand>` مدرجًا، مع `subenabled = t`. أصبح الخادم الاحتياطي الآن مشتركًا منطقيًا في Postgres 17.9.

## الخطوة 4: ترقية الخادم الاحتياطي إلى Postgres 18.3 باستخدام pg\_upgrade

الخطوة التالية هي عملية `pg_upgrade` قياسية من PG 17.9 إلى PG 18.3، مع خطوة مسبقة بالغة الأهمية: **تعطيل الاشتراكات على المشترك قبل تشغيل pg\_upgrade**، وفقًا لوثائق الترقية الرسمية.[^14] إذا تخطيت هذه الخطوة، فسترفض الترقية ترحيل حالة الاشتراك.

```bash
Docker exec pg17-standby psql -U postgres -d shop \
  -c "ALTER SUBSCRIPTION pg_createsubscriber_5_<rand> DISABLE;"
Docker compose stop standby
```

استبدل `<rand>` باللاحقة الفعلية من `pg_subscription`. مع استخدام `--all`، سيكون لديك اشتراك واحد لكل قاعدة بيانات مستخدم — قم بتعطيل كل واحد منها.

تتطلب أداة `pg_upgrade` توفر كل من ملفات PostgreSQL الثنائية القديمة والجديدة على نفس المضيف. لا توفر صورة Docker الرسمية لـ `postgres:17.9` ولا لـ `postgres:18.3` كلا الإصدارين، لذا سنقوم ببناء صورة صغيرة مزدوجة الإصدار. ضع ملف `Dockerfile.upgrade` هذا بجوار ملف compose:

```dockerfile
# Dockerfile.upgrade
FROM postgres:18.3-bookworm
RUN apt-get update \
 && apt-get install -y --no-install-recommends postgresql-17 \
 && rm -rf /var/lib/apt/lists/*
```

يوفر مستودع PGDG المكون داخل صورة `postgres:18.3-bookworm` الرسمية بالفعل كل إصدار رئيسي مدعوم،[^19] لذا فإن إضافة حزمة `postgresql-17` تمنحنا `/usr/lib/postgresql/17/bin/` جنبًا إلى جنب مع `/usr/lib/postgresql/18/bin/` في نفس الصورة. قم ببنائها مرة واحدة:

```bash
Docker build -t pg-upgrade:17-to-18 -f Dockerfile.upgrade .
```

الآن قم بتشغيل `pg_upgrade` مقابل دليل بيانات الخادم الاحتياطي. لاحظ أن PostgreSQL 18 قام بتغيير موقع `PGDATA` الافتراضي لصورة Docker الخاصة به — فهو يعيش الآن في `/var/lib/postgresql/18/Docker` بحيث يمكن لترقيات الإصدارات الرئيسية المستقبلية تركيب وحدة تخزين مشتركة واحدة.[^20] نحن نتبع نفس الاتفاقية هنا، حيث نخبر `pg_upgrade` بكتابة المجموعة (cluster) الجديدة في `/var/lib/postgresql/18/Docker`. يتم تشغيل البرنامج النصي كجذر (root) حتى يتمكن من تنفيذ `chown` على وحدة التخزين التي تم تركيبها حديثًا، ثم ينتقل إلى مستخدم `postgres` عبر `gosu` (المثبت مسبقًا في الصورة الرسمية) لعمليات Postgres الفعلية:

```bash
Docker volume create pg18-zdt_subscriber_v18

Docker run --rm --network pg18-zdt_default \
  -v pg18-zdt_standby_data:/var/lib/postgresql/data \
  -v pg18-zdt_subscriber_v18:/var/lib/postgresql/18 \
  pg-upgrade:17-to-18 bash -c '
    chown -R postgres:postgres /var/lib/postgresql/18 &&
    gosu postgres /usr/lib/postgresql/18/bin/initdb \
      -D /var/lib/postgresql/18/Docker \
      -U postgres --auth-local=trust --auth-host=scram-sha-256 &&
    printf "wal_level = logical\nmax_replication_slots = 10\nmax_active_replication_origins = 10\n" \
      | gosu postgres tee -a /var/lib/postgresql/18/Docker/postgresql.conf &&
    cd /tmp &&
    gosu postgres /usr/lib/postgresql/18/bin/pg_upgrade \
      --old-bindir=/usr/lib/postgresql/17/bin \
      --new-bindir=/usr/lib/postgresql/18/bin \
      --old-datadir=/var/lib/postgresql/data \
      --new-datadir=/var/lib/postgresql/18/Docker \
      --link
  '
```

ثلاثة أشياء يجب ملاحظتها:

- `--link` يقوم بإنشاء روابط صلبة (hardlinks) لملفات البيانات في المجموعة الجديدة، وهذا هو السبب في أن `pg_upgrade` سريع حتى في المجموعات الكبيرة. لا يمكنك بدء تشغيل مجموعة PG 17 القديمة بعد اكتمال ترقية `--link`، لذا فإن الخادم الاحتياطي أصبح الآن بشكل دائم نسخة Postgres 18.
- سطر `printf | tee` يكتب إعدادات GUC الثلاثة التي تتحقق منها `pg_upgrade` قبل أن تقوم بترحيل الفتحات المنطقية — تحتاج المجموعة الجديدة إلى `wal_level = logical` و `max_replication_slots` على الأقل بنفس حجم عدد الفتحات في المجموعة القديمة.[^14]
- يستمر الناشر في قبول عمليات الكتابة طوال الوقت الذي يتم فيه تشغيل `pg_upgrade` — سيتم إعادة تشغيل تلك التغييرات من خلال الفتحة المنطقية الموجودة في اللحظة التي تظهر فيها المجموعة التي تمت ترقيتها.

أضف المجموعة التي تمت ترقيتها إلى ملف compose الخاص بك. اضبط `PGDATA` صراحةً حتى تكتشف نقطة الإدخال الرسمية دليل البيانات في موقع PG 18 الجديد، وقم بتثبيت إعدادات GUC التي أشارت إليها الترقية:

```yaml
  subscriber_v18:
    image: postgres:18.3
    container_name: pg18-subscriber
    ports:
      - "5534:5432"
    environment:
      PGDATA: /var/lib/postgresql/18/Docker
    volumes:
      - subscriber_v18:/var/lib/postgresql/18
    command:
      - "postgres"
      - "-c"
      - "wal_level=logical"
      - "-c"
      - "max_replication_slots=10"
      - "-c"
      - "max_active_replication_origins=10"
      - "-c"
      - "listen_addresses=*"

volumes:
  publisher_data:
  standby_data:
    external: true
    name: pg18-zdt_standby_data
  subscriber_v18:
    external: true
    name: pg18-zdt_subscriber_v18
```

`max_active_replication_origins` هو إعداد GUC جديد في Postgres 18 — تم فصله عن `max_replication_slots` حتى يتمكن المشتركون من تحديد حجم تتبع أصل النسخ الاحتياطي بشكل مستقل. قيمته الافتراضية هي 10 ويتم ضبطه عند بدء تشغيل الخادم.[^18] قم بتشغيل المجموعة وأعد تمكين الاشتراك:

```bash
Docker compose up -d subscriber_v18
Docker exec pg18-subscriber psql -U postgres -d shop \
  -c "ALTER SUBSCRIPTION pg_createsubscriber_5_<rand> ENABLE;"
```

## الخطوة 5: مزامنة المتتاليات (sequences) والتحويل النهائي (cut over)

لا يقوم النسخ المتماثل المنطقي (Logical replication) بنسخ المتسلسلات (sequences) في Postgres 18.[^15] تتدفق **قيم** الأعمدة من نوع `IDENTITY` و `serial` المدعومة بمتسلسلات بشكل جيد لأنها تنتقل كبيانات أعمدة عادية، لكن المؤشر الداخلي للمتسلسلة على المشترك (subscriber) يظل يشير إلى قيمة البداية. أول عملية إدخال (insert) مباشرة على العنقود (cluster) الجديد ستعطي `id` موجوداً بالفعل — مما يؤدي فوراً إلى خطأ مفتاح مكرر (duplicate-key error).

مزامنة المتسلسلات مدرجة في خارطة الطريق لنسخة Postgres 19،[^16] ولكن بالنسبة لـ 18 عليك القيام بذلك يدوياً. قم بإنشاء سكربت `setval()` على الناشر (publisher) وقم بتطبيقه على المشترك **مباشرة قبل** عملية التحويل (cutover)، مع ترك هامش (buffer) لاستيعاب أي معاملات قيد التنفيذ:

```sql
-- Run on publisher, capture output
SELECT format(
  'SELECT pg_catalog.setval(%L, %s, true);',
  schemaname || '.' || sequencename,
  COALESCE(last_value, 1) + 1000
)
FROM pg_sequences
WHERE schemaname NOT IN ('pg_catalog','information_schema')
ORDER BY schemaname, sequencename;
```

هامش الـ `+ 1000` هو هامش الأمان التقليدي — فهو يضمن أن قيمة المتسلسلة التالية للمشترك تتجاوز أي قيمة قدمها الناشر بالإضافة إلى أي شيء قيد التنفيذ خلال نافذة التحويل الخاصة بك.

قم بتطبيق عبارات `setval()` التي تم إنشاؤها على `pg18-subscriber`، ثم قم بإجراء التحويل:

```bash
# 1. Block writes on publisher (revoke INSERT/UPDATE/DELETE for app role,
#    or flip the load balancer to read-only). Keep reads alive.
# 2. Wait for the subscription to drain to the publisher's current LSN:
Docker exec pg18-subscriber psql -U postgres -d shop -c "
  SELECT subname, latest_end_lsn FROM pg_stat_subscription;"
Docker exec pg17-publisher psql -U postgres -c "SELECT pg_current_wal_lsn();"
# 3. When latest_end_lsn >= pg_current_wal_lsn, redirect application traffic
#    to port 5534 (the PG 18 cluster).
# 4. Drop the subscription and the publisher's slot to clean up.
Docker exec pg18-subscriber psql -U postgres -d shop -c "
  ALTER SUBSCRIPTION pg_createsubscriber_5_<rand> DISABLE;
  ALTER SUBSCRIPTION pg_createsubscriber_5_<rand> SET (slot_name = NONE);
  DROP SUBSCRIPTION pg_createsubscriber_5_<rand>;"
```

التطبيق الآن يقوم بالكتابة في Postgres 18.3.

## التحقق

هناك فحصان يخبرانك أن الترقية تمت بنجاح. أولاً، يجب أن يكون كل جدول تم نسخه في حالة `r` (جاهز) في `pg_subscription_rel`:[^17]

```sql
SELECT srrelid::regclass AS table_name, srsubstate, srsublsn
FROM pg_subscription_rel
ORDER BY srrelid::regclass;
```

أي حالة `srsubstate` بخلاف `r` في أي صف تعني أن الجدول لا يزال قيد المزامنة أو عالقاً. ثانياً، يجب أن تكون مؤشرات المتسلسلة متقدمة على أي معرفات (IDs) موجودة — يجب أن يكون `MAX(id)` في كل جدول أقل من `nextval('<seq>')`. قم بإدخال صف تجريبي على العنقود الجديد وتأكد من عدم وجود خطأ مفتاح مكرر.

## الأخطاء الشائعة

**`pg_createsubscriber: error: target server must be shut down`** — الخادم الاحتياطي (standby) لا يزال يعمل. قم بتنفيذ `Docker compose stop standby` وأعد التشغيل. يرفض الملف الثنائي العمل على عنقود نشط عن قصد.[^1]

**`max_active_replication_origins must be at least N`** — إعداد GUC الجديد في PG 18، والذي تم فصله عن `max_replication_slots` في PG 18، قيمته الافتراضية 10 وهو المكافئ لكل مشترك لمحاسبة فتحات النسخ المتماثل. أعد التشغيل مع زيادة القيمة.[^18]

**`subscription has tables in srsubstate = 's'` بعد pg\_upgrade** — يتطلب `pg_upgrade` أن يكون كل جدول اشتراك في حالة `i` (بدء) أو `r` (جاهز) قبل تشغيله.[^14] انتظر حتى تنتهي المزامنة الأولية قبل الترقية.

**`duplicate key value violates unique constraint` مباشرة بعد التحويل** — المتسلسلات. أعد تشغيل سكربت `setval()` مع هامش أكبر؛ هذا من بين أكثر الأخطاء شيوعاً في عمليات التحويل الخاصة بالنسخ المتماثل المنطقي.[^15]

**يبدو النسخ المتماثل عالقاً، `pg_replication_slots.confirmed_flush_lsn` لا يتقدم** — تأكد من أن عامل التطبيق (apply worker) نشط على المشترك (يجب أن يظهر `pg_stat_subscription` قيمة `received_lsn` غير فارغة)، وتحقق من `pg_stat_database_conflicts` على الناشر. إذا كان WAL الخاص بالناشر يمتلئ لأن المشترك متأخر جداً، فقم بزيادة `max_slot_wal_keep_size` أو أصلح ما يمنع التطبيق (معاملة طويلة الأمد، تعارض قفل، مفتاح أساسي مفقود).

## التراجع (Rollback)

نظراً لأن الترقية أبقت خادم Postgres 17.9 الأساسي الأصلي يعمل ويقبل عمليات الكتابة طوال الوقت، فإن **التراجع هو مجرد تبديل لسلسلة الاتصال (connection-string)**. إذا بدا أن هناك خطأ ما في Postgres 18.3 خلال دقائق من التحويل، فقم بإعادة توجيه التطبيق مرة أخرى إلى المنفذ 5532. لا يزال خادم PG 17.9 الأساسي هو المرجع؛ البيانات الوحيدة التي لا يملكها هي ما كتبه التطبيق في PG 18.3 بعد التحويل. للحصول على شبكة أمان حقيقية ثنائية الاتجاه، قم بإعداد اشتراك منطقي عكسي PG 18 ← PG 17 قبل التحويل (الناشر على العنقود الجديد، والمشترك على القديم)، بحيث تتدفق عمليات الكتابة بعد التحويل مرة أخرى. هذا يستحق ساعة إضافية من الإعداد لأعباء عمل OLTP الحرجة.

## الخطوات التالية

نفس سير عمل `pg_createsubscriber` يدعم عمليات النشر من نوع blue-green — قم بإعداد نسخة PG 18 متماثلة، استنزف القراءات، حول الكتابات، وأوقف تشغيل الخادم الأساسي القديم وفقاً لجدول زمني بدلاً من الساعة 3 صباحاً. ادمجه مع [PgBouncer أو Supabase Supavisor أمام Postgres](https://nerdleveltech.com/production-postgres-pooling-pgbouncer-Supabase-supavisor-tutorial) بحيث يكون تبديل الاتصال عبارة عن إعادة تحميل لتكوين المجمع (pooler) بدلاً من إعادة نشر التطبيق. إذا كنت بحاجة إلى أحداث واجهة مستخدم في الوقت الفعلي فوق هذه المجموعة، فإن [Postgres LISTEN/NOTIFY للتواجد المباشر](https://nerdleveltech.com/postgres-listen-notify-realtime-presence-tutorial) يضاف فوقها بسلاسة دون أي نسخ متماثل جديد. وإذا كنت تريد بيئة تطوير كاملة تعتمد على Docker-Compose خلف HTTPS بمعايير الإنتاج، فراجع [دليل الوكيل العكسي Caddy](https://nerdleveltech.com/caddy-reverse-proxy-Docker-compose-production-https-tutorial).

[^1]: توثيق PostgreSQL 18، *pg\_createsubscriber*. <https://www.postgresql.org/docs/current/app-pgcreatesubscriber.html>

[^2]: توثيق PostgreSQL 18، *الفصل 29. النسخ المتماثل المنطقي — الترقية*. <https://www.postgresql.org/docs/current/logical-replication-upgrade.html>

[^3]: توثيق PostgreSQL 18، *pg\_upgrade*. <https://www.postgresql.org/docs/current/pgupgrade.html>

[^4]: أخبار PostgreSQL، *إصدار PostgreSQL 18.3، 17.9، 16.13، 15.17، و 14.22!* (26 فبراير 2026). <https://www.postgresql.org/about/news/postgresql-183-179-1613-1517-and-1422-released-3246/>

[^5]: توثيق PostgreSQL 18، أداة *pg\_createsubscriber* — مفاتيح `--all`، و `--clean`، و `--enable-two-phase`. <https://www.postgresql.org/docs/current/app-pgcreatesubscriber.html>

[^6]: Docker، *مواصفات Compose*. <https://docs.Docker.com/compose/>

[^7]: توثيق PostgreSQL 18، *الفصل 29. النسخ المتماثل المنطقي (Logical Replication)*. <https://www.postgresql.org/docs/current/logical-replication.html>

[^8]: pgPedia، أداة *pg\_createsubscriber*. <https://pgpedia.info/p/pg_createsubscriber.html>

[^9]: توثيق PostgreSQL 18، *19.5. سجل الكتابة المسبقة (Write Ahead Log) — wal\_level*. <https://www.postgresql.org/docs/current/runtime-config-wal.html>

[^10]: توثيق PostgreSQL 18، *19.5. سجل الكتابة المسبقة (Write Ahead Log)* — الترقية التلقائية الفعالة لمستوى WAL عند وجود فتحة منطقية (logical slot). <https://www.postgresql.org/docs/current/runtime-config-wal.html>

[^11]: توثيق PostgreSQL 18، *pg\_createsubscriber* — المتطلبات الأساسية لـ `max_replication_slots` و `max_wal_senders` على المصدر. <https://www.postgresql.org/docs/current/app-pgcreatesubscriber.html>

[^12]: توثيق PostgreSQL 18، *pg\_basebackup*. <https://www.postgresql.org/docs/current/app-pgbasebackup.html>

[^13]: pgPedia، *pg\_createsubscriber* — اتفاقية تسمية الفتحات (slots) `pg_createsubscriber_%u_%x`. <https://pgpedia.info/p/pg_createsubscriber.html>

[^14]: توثيق PostgreSQL 18، *الفصل 29.13. الترقية* — متطلبات pg\_upgrade الأساسية لعناقيد النسخ المتماثل المنطقي، بما في ذلك تعطيل الاشتراكات قبل الترقية. <https://www.postgresql.org/docs/current/logical-replication-upgrade.html>

[^19]: مستودع PostgreSQL APT، *PostgreSQL Apt Repository* — يخدم جميع الإصدارات الرئيسية المدعومة حالياً. <https://wiki.postgresql.org/wiki/Apt>

[^20]: Docker-library/postgres GitHub PR / مناقشة — تغيير VOLUME في صورة Postgres 18 إلى `/var/lib/postgresql` لتمكين الترقيات الرئيسية جنباً إلى جنب عبر `pg_upgrade --link`. <https://GitHub.com/Docker-library/postgres/issues/37>

[^18]: توثيق PostgreSQL 18، *19.6. النسخ المتماثل — `max_active_replication_origins`* (جديد في PG 18). <https://www.postgresql.org/docs/current/runtime-config-replication.html>

[^15]: توثيق PostgreSQL 18، *29.8. القيود* — لا يتم نسخ التسلسلات (sequences) والكائنات الكبيرة (large objects). <https://www.postgresql.org/docs/current/logical-replication-restrictions.html>

[^16]: مدونة depesz، *في انتظار PostgreSQL 19 — مزامنة التسلسلات في النسخ المتماثل المنطقي* (نوفمبر 2025). <https://www.depesz.com/2025/11/11/waiting-for-postgresql-19-sequence-synchronization-in-logical-replication/>

[^17]: توثيق PostgreSQL 18، *كتالوج نظام pg\_subscription\_rel*. <https://www.postgresql.org/docs/current/catalog-pg-subscription-rel.html>
