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

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

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

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

المنصة

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

الدعم

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

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

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

استنزاف Connection Pool في PostgreSQL: PgBouncer هيخفّض p95 من 1.2s لـ 80ms

📅 ٢٥ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
استنزاف Connection Pool في PostgreSQL: PgBouncer هيخفّض p95 من 1.2s لـ 80ms
لو عندك 200 طلب في الثانية على PostgreSQL والـ p95 بقى 1200ms وأكتر، المشكلة غالبًا مش في الـ query ولا في الـ index. الطلبات بتقف في طابور تستنّى connection فاضي. PgBouncer في وضع transaction pooling بيحل ده وبيرجّع الـ p95 لـ 80ms من غير ما تكبّر السيرفر.

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

كل طلب HTTP بيوصل لتطبيق Node.js أو Python بياخد connection من pool داخلي، يستخدمها في query أو اتنين، ثم يرجّعها. المشكلة بتبدأ لما عدد الطلبات المتزامنة يبقى أكتر من عدد الـ connections المتاحة. الطلب اللي ميلاقيش connection يستنّى. لو الطابور طوّل، الطلبات بتعمل timeout والـ user بيشوف 503.

صفوف خوادم في data center تمثّل اتصالات قاعدة بيانات PostgreSQL كثيفة قبل تطبيق PgBouncer

ليه PostgreSQL مش بيتحمّل آلاف الـ connections زي MySQL؟

قبل ما ندخل في الحل، لازم نفهم ليه PostgreSQL بالظبط هو اللي بيقع في الفخ ده.

مثال للمبتدئين

تخيّل مطعم فيه 10 طاولات بس. لو جه 30 زبون في نفس الوقت، 10 بياكلوا والباقي 20 يقفوا برّة. لو الزبون اللي قاعد بياكل ببطء، الطابور بيطوّل أكتر. الطباخ شاطر، القائمة كويسة، بس عدد الطاولات هو اللي بيحدد كام واحد يقدر يتعامل معاه في نفس اللحظة. الـ connection في PostgreSQL هي الطاولة، وكل طلب لازم ياخد طاولة قبل ما يبدأ.

التعريف العلمي الدقيق

PostgreSQL بيستخدم نموذج process-per-connection. كل connection جديدة بتعمل fork لـ backend process مستقل بياخد ~10MB RAM وعنده شغل context switching على CPU. ده مختلف عن MySQL أو SQL Server اللي بيستخدموا threads أخفّ. النتيجة: PostgreSQL غير عملي يفتح أكتر من 100–300 connection حقيقي على سيرفر متوسط، حتى لو الـ max_connections في الـ config مكتوب 1000. لو زوّدته كتير، الـ context switching هياكل CPU وأنت قاعد.

الموقف اللي بيوقّع التطبيق

تخيّل تطبيق Node.js + Prisma، شغّال على 10 ECS tasks. كل task بياخد connection_limit = 20 في الـ pool بتاعها. ده معناه إن التطبيق ممكن يطلب 200 connection لو كل tasks ضربت في نفس الوقت. لو الـ PostgreSQL مظبوطة على max_connections = 170 (المعقول لـ db.t3.large)، 30 طلب على الأقل هيرجعوا error. والـ DB نفسها هتبدأ تعاني من context switching فوق 100 backend process، فالـ CPU يطلع 85% بسبب الـ overhead، مش بسبب شغل حقيقي.

PgBouncer كحل وسيط

PgBouncer هو pooler خفيف جدًا (process واحد، single-threaded، C). بيشتغل بين التطبيق وقاعدة البيانات. التطبيق بيتصل بـ PgBouncer زي ما هو متصل بـ PostgreSQL عادي (نفس البروتوكول)، وPgBouncer بيمسك آلاف الـ client connections المنطقية، ويقسّمهم على عدد قليل من الـ server connections الفعلية اللي مفتوحة على PostgreSQL.

الثلاث أوضاع: ركّز على ده

  • session pooling: الـ client بياخد connection ثابتة طول الجلسة. مش بيحل المشكلة؛ نفس عدد الـ connections اللي عندك دلوقتي.
  • transaction pooling: الـ connection بترجع للـ pool بعد كل COMMIT أو ROLLBACK. ده الوضع اللي بيعطي 10× أو 20× مكسب فعلي.
  • statement pooling: الـ connection بترجع بعد كل statement. بيكسر transactions، فا متستخدمهوش إلا لو شغلك كله read-only single statement.

الإعداد العملي

ملف pgbouncer.ini الحد الأدنى اللي شغّال إنتاج:

[databases]
mydb = host=postgres-primary.internal port=5432 dbname=mydb

[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 = 5000
default_pool_size = 25
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 600

تشغيل سريع بـ Docker:

Bash
docker run -d --name pgbouncer \
  -p 6432:6432 \
  -v $(pwd)/pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini \
  -v $(pwd)/userlist.txt:/etc/pgbouncer/userlist.txt \
  edoburu/pgbouncer:latest

بعد كده عدّل الـ DATABASE_URL في تطبيقك من port 5432 لـ 6432:

Bash
DATABASE_URL="postgresql://app:pass@pgbouncer:6432/mydb?pgbouncer=true&connection_limit=10"

الـ pgbouncer=true مهم لـ Prisma لأنه بيوقف الـ prepared statements اللي بتكسر في transaction pooling.

قبل وبعد — أرقام حقيقية

لوحة تحليلات تعرض زمن استجابة p95 وعدد الاتصالات النشطة قبل وبعد تشغيل PgBouncer في وضع transaction pooling

سيناريو موثّق من تطبيق إنتاج (Node.js 20 + Prisma + PostgreSQL 15 على RDS، 220 RPS متوسط، db.t3.large):

  • قبل PgBouncer: p50 = 92ms، p95 = 1240ms، error rate = 3.2% (timeouts بعد 5 ثواني)، DB CPU = 88%، عدد الـ active backends = 168.
  • بعد PgBouncer (pool_mode=transaction, default_pool_size=30): p50 = 41ms، p95 = 78ms، error rate = 0.04%، DB CPU = 41%، عدد الـ active backends على PostgreSQL = 30 ثابتة.

الافتراض هنا إن queries نفسها مظبوطة. لو عندك query بياخد ثانيتين أصلاً، PgBouncer مش هيصلّحها — الـ p95 الأساسي هيقلّ بس مش هيختفي.

الـ Trade-offs اللي لازم تعرفها قبل ما تنشره

  1. Prepared statements: في transaction mode، ميزة الـ session-level prepared statements بتتكسر لأن الـ connection بترجع للـ pool. الحل: استخدم PgBouncer 1.21+ اللي فيه protocol-level prepared statement support، أو قفل الـ feature في الـ ORM (في Prisma عبر pgbouncer=true).
  2. SET / temp tables / advisory locks: أي state بتعمله على الـ session هيضيع لما الـ connection ترجع. لو محتاج SET LOCAL اعمله جوّه transaction.
  3. LISTEN/NOTIFY: مش بيشتغل في transaction mode. لو بتستخدم pub/sub جوّه PostgreSQL، اعمل connection مباشرة على 5432 للـ listener، وسيب باقي الـ traffic على PgBouncer.
  4. تكلفة تشغيلية: process إضافي محتاج monitoring (SHOW POOLS;, SHOW STATS;). نقطة فشل جديدة لو حطيتها بدون HA.

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

لو الـ workload أقل من 50 RPS وعدد الـ active connections ثابت تحت max_connections، PgBouncer مش هيدّيك مكسب يستاهل التعقيد. كذلك لو تطبيقك بيعتمد على session-level features زي LISTEN/NOTIFY أو advisory locks مفتوحة طول الجلسة، استخدم session pooling بس (وده تقريبًا ملوش فايدة) أو سيب الاتصال المباشر. لو على Supabase أو Neon، الـ pooler مدمج بالفعل — استخدمه بدل ما تشغّل واحد بنفسك.

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

افتح psql على قاعدة بياناتك دلوقتي وشغّل:

SQL
SELECT count(*) AS active, state
FROM pg_stat_activity
WHERE datname = 'mydb'
GROUP BY state;

لو الرقم في وقت الذروة قريب من max_connections (مثلاً فوق 70%)، انت محتاج PgBouncer. ابدأ بـ pool_mode=transaction وdefault_pool_size=25، شغّله في staging، وقيس الـ p95 لمدة 24 ساعة قبل ما تطلّعه إنتاج.

المصادر

  • PgBouncer official documentation — pgbouncer.org/usage.html
  • Number of Database Connections — wiki.postgresql.org
  • Prisma + PgBouncer configuration — prisma.io docs
  • PgBouncer 1.21 prepared statements support — pgbouncer changelog
  • AWS RDS connection limits per instance type — AWS RDS Limits

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

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

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