أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
Optimizing بالعربي

PgBouncer للمتوسط: شغّل 1000 طلب على 25 connection Postgres

📅 ٨ مايو ٢٠٢٦⏱ 6 دقائق قراءة
PgBouncer للمتوسط: شغّل 1000 طلب على 25 connection Postgres

مستوى المقال: متوسط — بيفترض إنك شغّلت PostgreSQL في إنتاج مرة على الأقل، شفت رسالة too many connections أو خايف منها، وفاهم الفرق بين الـ application server والـ DB.

لو السيرفر بتاعك Node.js أو Django مع PostgreSQL، وفجأة بدأ يرجّع FATAL: sorry, too many clients already عند 100 connection مع إن السيرفر فيه 64 جيجا RAM فاضية، المشكلة مش في حجم الـ DB. المشكلة في تصميم PostgreSQL نفسه: كل connection بيفتح process كامل بياكل 9-12 ميجا. PgBouncer بيخلّي 1000 طلب من التطبيق يمشوا على 25 connection فعلي، فالاستهلاك بينزل من 2.3 جيجا لـ 310 ميجا، وP95 latency من 142ms لـ 38ms.

PgBouncer وحل مشكلة الـ Connection Pool في PostgreSQL

المشكلة باختصار

PostgreSQL بيستخدم نموذج process-per-connection. كل عميل بيفتح اتصال = process مستقل في نظام التشغيل. ده آمن جدًا (memory isolation كاملة) لكن مكلف. الإعداد الافتراضي لـ max_connections هو 100، ولو زوّدته لـ 1000 السيرفر مش هيقع — هيعيش، لكن هيستهلك 12 جيجا RAM في الـ idle بس، وكل query هيبطئ بسبب الـ context switching بين العمليات.

صفوف من خوادم قاعدة بيانات متوازية تمثل آلاف الـ connections التي يديرها PgBouncer كطبقة تجميع

المثال للمبتدئ: شبّاك التذاكر في السينما

تخيّل سينما فيها 25 شبّاك بيع تذاكر، وفجأة جه 1000 زبون. لو كل زبون مسك شبّاك لنفسه، الشبابيك خلصت بعد 25 زبون والباقي وقف ساعتين بدون أي حركة. لكن لو فيه منظّم واقف بيقول "خد دورك في الطابور، أول ما الشبّاك يفضى تروحله"، نفس الـ 25 شبّاك بيخدموا الـ 1000 في وقت أقل بكتير. ليه؟ لأن المعاملة الفعلية على الشبّاك بتاخد 30 ثانية بس، فالشبّاك الواحد بيخدم 120 زبون في الساعة بدل ما يفضل محجوز لزبون واحد بيقرأ القائمة.

PgBouncer هو المنظّم ده بالظبط. تطبيقاتك (الـ 1000 زبون) بتفتح 1000 connection على PgBouncer (الفتح ده مجاني تقريبًا، استهلاك أقل من 50KB). PgBouncer من ناحيته بيفتح 25 connection بس على PostgreSQL، وبيوزّع الـ queries عليهم بالتتابع. بكدا الـ DB ما بتشوفش الزحام أصلاً.

التعريف العلمي: ليه connection في Postgres مكلف

أول ما عميل يعمل connect() على Postgres بيحصل التتابع ده:

  1. الـ postmaster process بيستقبل الطلب على البورت 5432.
  2. بيعمل fork() ليخلق backend process جديد للعميل.
  3. الـ backend بيعمل authentication ضد pg_hba.conf، يقرأ system catalogs، ويحجز ذاكرة لـ work_mem و temp_buffers.
  4. الإجمالي بياخد بين 1.3ms و 5ms لكل connection جديد.

المشكلة الأكبر إن الـ process ده بيفضل عايش طول مدة الـ connection حتى لو مفيش query شغّال. ولو التطبيق بيفتح ويقفل connection لكل request (anti-pattern شائع جدًا في PHP و serverless)، الـ overhead بياكل 30-40% من زمن الاستجابة.

الحل: PgBouncer بإعداد transaction pool

PgBouncer process خفيف مكتوب بـ C، استهلاكه ≈ 2 ميجا، بيقعد بين تطبيقك والـ DB. بيدعم 3 أوضاع: session، transaction، وstatement. أهمهم وأكثرهم استخدامًا في الإنتاج هو transaction mode: العميل بيمسك الـ connection بس طول مدة الـ transaction، وأول ما يحصل COMMIT أو ROLLBACK بترجع للـ pool فورًا. ده بيخلّي 1000 client يتشاركوا 25 connection فعلي طول ما مفيش transaction طويل.

; ملف /etc/pgbouncer/pgbouncer.ini
[databases]
appdb = host=127.0.0.1 port=5432 dbname=appdb

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 600

بعد كده غيّر الـ DATABASE_URL في تطبيقك من البورت 5432 (Postgres مباشرة) لـ 6432 (PgBouncer):

Bash
# قبل
DATABASE_URL=postgres://app:pwd@db.local:5432/appdb

# بعد
DATABASE_URL=postgres://app:pwd@db.local:6432/appdb
مجموعة أنابيب موصولة تمثل تجميع 1000 طلب تطبيق على 25 connection فعلي للـ PostgreSQL

الأرقام الفعلية على إنتاج

الإعداد الاختباري: PostgreSQL 16 على VPS Hetzner CCX23 (4 vCPU، 16GB RAM، NVMe)، تطبيق Node.js مع pg-promise بـ 8 instances خلف NGINX، حمل 1000 طلب/ثانية لمدة 10 دقائق متواصلة:

  • بدون PgBouncer مع max_connections=200: استهلاك RAM للـ DB = 2.3 جيجا، P95 latency = 142ms، نسبة فشل 3.2% بسبب timeouts.
  • مع PgBouncer و default_pool_size=25: استهلاك RAM للـ DB = 310 ميجا، P95 latency = 38ms، نسبة فشل = 0%.

التوفير: 86% ذاكرة، 73% زمن استجابة. الفرق ده مش تحسين خوارزمي ولا upgrade للهارد، ده فقط لأن الـ idle processes في Postgres اختفت من المعادلة.

الـ Trade-offs اللي محدش بيقولها

  1. Session features بتموت في transaction mode. SET LOCAL، prepared statements (في إصدارات قبل PgBouncer 1.21)، LISTEN/NOTIFY، temporary tables — كلها بتنكسر لأن الـ connection مش مضمون تكون نفسها بين queries متتالية. الحل: استخدم session pool بقاعدة منفصلة للـ features دي.
  2. طبقة فشل إضافية. PgBouncer لو وقع، DB سليمة بس مفيش حد بيوصلها. شغّل instance تاني خلف HAProxy أو احتفظ بـ direct connection على البورت 5432 للـ admin tasks.
  3. الـ monitoring بيتعقّد. الأرقام في pg_stat_activity هتبقى دايمًا قريبة من 25. عشان تشوف الطابور الحقيقي لازم تدخل على PgBouncer admin interface وتعمل SHOW POOLS; و SHOW STATS;.
  4. الـ pool size اختيار حسّاس جدًا. الافتراض الشائع هو default_pool_size = (cpu_count × 2) + storage_count، حسب توصيات Brandur Leach. لو حطيت 200 هتاكل CPU في context switching، لو حطيت 5 هتعمل bottleneck على query واحد بطيء. ابدأ بـ 25 لـ DB متوسط واقيس cl_waiting في SHOW POOLS;.

متى لا تستخدم PgBouncer

تجاهل PgBouncer في الحالات دي:

  • تطبيقك بيشتغل على serverless (AWS Lambda، Cloudflare Workers، Vercel Functions) — استخدم RDS Proxy أو Neon أو Supabase اللي عندهم pooler مدمج، لأن PgBouncer self-hosted محتاج process دائم.
  • عدد الـ connections دايمًا أقل من 50 ومستقر — الـ overhead التشغيلي أعلى من الفايدة.
  • التطبيق بيعتمد بشكل أساسي على LISTEN/NOTIFY أو prepared statements في إصدار قبل 1.21 — هتقع في bugs غريبة وصعبة الـ debugging.
  • عندك ORM بيستخدم session-level features زي Sequelize transactions الطويلة من غير ما تتحكم فيها — لازم تراجع الكود قبل ما تدخل transaction pool.

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

افتح Postgres الإنتاجي عندك دلوقتي ونفّذ:

SQL
SELECT count(*), state FROM pg_stat_activity GROUP BY state;

لو شفت أكثر من 60% من الـ connections في حالة idle، انت محتاج PgBouncer النهارده مش بكرة. ركّبه على نفس السيرفر بـ apt install pgbouncer، انسخ الـ config فوق، وغيّر DATABASE_URL في app instance واحد فقط للـ benchmark. لو الأرقام عجبتك انقل الباقي. لو ماعجبتكش، الرجوع تغيير بورت في متغير بيئة واحد.

المصادر

  • توثيق PgBouncer الرسمي — pgbouncer.org/config.html
  • PostgreSQL Wiki: Number Of Database Connections — wiki.postgresql.org
  • Brandur Leach، "Postgres Connection Scaling" — brandur.org/postgres-connections
  • توثيق pg_stat_activity الرسمي — postgresql.org/docs/16/monitoring-stats
  • Crunchy Data PGO، "PgBouncer transaction mode best practices" — access.crunchydata.com/documentation

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة