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

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

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

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

المنصة

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

الدعم

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

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

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

PgBouncer Transaction Pooling للمتوسط: من 8,400 connection error/دقيقة إلى صفر

📅 ١٤ مايو ٢٠٢٦⏱ 5 دقائق قراءة
PgBouncer Transaction Pooling للمتوسط: من 8,400 connection error/دقيقة إلى صفر

مستوى المقال: متوسط — مناسب لمطورين شغّالين على Node.js أو Python مع PostgreSQL ووصلوا لحاجز الـ connections في الإنتاج.

لو الـ API بتاع منصتك بيرجّع FATAL: sorry, too many clients already في ساعات الذروة، PostgreSQL مش ضعيف — انت بتفتح 1,200 connection على DB قابل لـ 100 فقط. PgBouncer 1.23 في transaction mode بيخلّي 1,000 طلب متزامن يشتغلوا على 25 connection حقيقي، وبيقلّل connection errors من 8,420 في الدقيقة لـ صفر، مع نزول P95 latency من 480ms لـ 28ms.

PgBouncer Transaction Pooling: تشريح المشكلة والحل العملي

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

كل process في Node.js cluster بيفتح pg-Pool خاص بيه، الافتراضي 10 connections. لو عندك 6 instances من الـ API على Kubernetes، بتطلع 60 connection ثابتة من غير ما حد يدخل الموقع. وقت الذروة، الـ pg-Pool بيفتح connections إضافية لأي query، وفجأة بتعدّي حاجز max_connections في postgresql.conf اللي افتراضه 100.

كل connection في Postgres بياخد حوالي 9MB ذاكرة (backend process)، وبيستهلك file descriptor، وبيدخل في contention على ProcArrayLock. النتيجة المباشرة: زمن الـ query بيطلع من 12ms لـ 480ms حتى لو الـ query نفسها بسيطة، لأن الـ scheduling بقى الـ bottleneck مش الـ disk.

رفوف خوادم قواعد بيانات بكابلات ألياف ضوئية تمثّل تشبع PostgreSQL بالـ connections قبل استخدام PgBouncer

تشبيه للمبتدئ: مطعم فيه 100 طاولة و1,200 زبون

تخيّل مطعم بـ 100 طاولة (دي هي max_connections). لو دخل 1,200 زبون مرة واحدة، الـ 1,100 الباقيين هيقفوا برّه ويلغوا الطلب (ده الـ connection refused). الحل التقليدي إنك تشتري المطعم اللي جنبك عشان تزوّد طاولات — ده غالي وبيحتاج RAM وCPU إضافي على السيرفر.

PgBouncer هو الـ host اللي بيقف على الباب. بيقول للزبون: "استنى على الكنبة، الطاولة جاية في 80 مللي ثانية". الزبون بياخد طاولة بس وقت ما هو طالبها فعلاً (transaction)، ولما يخلّص، الطاولة بترجع للي بعده فوراً من غير ما حد ينضف الكرسي. النتيجة: 100 طاولة بتخدم 1,200 زبون من غير ما حد يقف برّه.

الشرح العلمي: ليه Transaction Mode الأهم

PgBouncer بيقدّم 3 modes للـ pooling حسب التوثيق الرسمي:

  • session pooling: الـ client بياخد connection طول مدة جلسته. ده مش بيحل المشكلة، بس بيوحّد الإدارة.
  • transaction pooling: الـ connection بيتحجز بس وقت ما في transaction مفتوحة. أوقف الـ transaction (COMMIT أو ROLLBACK)، الـ connection بيرجع للـ pool. ده الـ mode اللي بيعمل المعجزة.
  • statement pooling: الـ connection بيرجع بعد كل statement. ممنوع فيه transactions متعدّدة العبارات تمامًا.

الافتراض في الكلام ده: تطبيقاتك stateless زي REST APIs، transactions قصيرة (أقل من 50ms)، ومش بتعتمد على session-level state زي SET العادي أو advisory locks الجلسية. لو الـ workload بتاعك من النوع ده، انت أصلًا المرشّح الطبيعي لـ transaction pooling.

إعداد PgBouncer 1.23 شغّال — قابل للنسخ

ملف /etc/pgbouncer/pgbouncer.ini:

[databases]
production = host=10.0.1.5 port=5432 dbname=production

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

server_idle_timeout = 600
server_lifetime = 3600
query_wait_timeout = 120

stats_period = 60

التطبيق Node.js بيتصل على port 6432 بدل 5432، والباقي زي ما هو من غير لمس logic:

JavaScript
import { Pool } from 'pg';

const pool = new Pool({
  host: 'pgbouncer.internal',
  port: 6432,
  database: 'production',
  user: 'app_user',
  password: process.env.DB_PASSWORD,
  max: 1000,
  idleTimeoutMillis: 30000,
});

export async function getUser(id) {
  const result = await pool.query(
    'SELECT id, email, plan FROM users WHERE id = $1',
    [id]
  );
  return result.rows[0];
}

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

الافتراض: 6 instances من API على Kubernetes (each pg-Pool max=170)، PostgreSQL 16 على RDS db.m6i.xlarge (4 vCPU, 16GB RAM)، حمل ذروة 1,240 req/sec على endpoint authentication.

لوحة مراقبة تعرض رسم بياني لزمن الاستجابة قبل وبعد تشغيل PgBouncer في وضع transaction pooling
المقياسقبل PgBouncerبعد PgBouncer
active backend connections96 / 10022 / 100
connection errors / دقيقة8,4200
P95 query latency480ms28ms
Postgres RAM usage14.2 GB3.8 GB
زمن إضافي من PgBouncer—~1.4ms

الـ Trade-offs اللي مش بتلاقيها في التوثيق

  1. Prepared statements server-side بتقع. الـ connection اللي حضّرت الـ statement مش هي اللي هتنفّذها. الحل: PostgreSQL 14+ مع max_prepared_statements = 100 في PgBouncer، أو خلّي ORM يستخدم simple query mode (في pg-node: queryMode: 'simple').
  2. LISTEN/NOTIFY ميشتغلش. الـ connection بترجع للـ pool فالـ subscription بتضيع. لو محتاجها، خصّص PgBouncer منفصل على session mode على port مختلف للـ events فقط.
  3. SET LOCAL بس، مفيش SET. أي session variable لازم تكون جوّه transaction باستخدام SET LOCAL عشان متسربش لـ client تاني بياخد نفس الـ connection بعد ثوانٍ.
  4. المراقبة بتتعقّد. الـ pg_stat_activity هيوريك 25 connection من PgBouncer مش 1,000 client حقيقي. لازم تستخدم SHOW POOLS و SHOW STATS داخل PgBouncer admin console.

متى لا تستخدم Transaction Pooling

تطبيقات BI أو ETL اللي بتستخدم transactions طويلة (10+ دقائق) — هتقفل الـ pool وتمنع باقي الـ workload. كذلك التطبيقات اللي بتعتمد على temporary tables عبر transactions متعدّدة، أو cursors مفتوحة بين requests. لو DB بتاعك أقل من 60 connection نشط في وقت الذروة، PgBouncer بيضيف 1-2ms latency بدون فائدة حقيقية — أبسط حاجة تكبّر max_connections لـ 200 وخلاص.

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

شغّل الـ query ده على PostgreSQL بتاعك دلوقتي عشان تشوف لو فعلًا محتاج PgBouncer:

SQL
SELECT count(*) AS active_connections,
       (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_conn,
       ROUND(100.0 * count(*) /
         (SELECT setting::int FROM pg_settings WHERE name = 'max_connections'), 1) AS pct_used
FROM pg_stat_activity
WHERE state = 'active';

لو pct_used بيعدّي 70% في الذروة، نزّل PgBouncer 1.23 بـ apt install pgbouncer، اعمل الـ config اللي فوق، وغيّر port التطبيق من 5432 لـ 6432. لو حصلت مشكلة بعد التطبيق، شغّل SHOW POOLS; داخل psql -p 6432 pgbouncer وابعتلي الـ output.

المصادر

  • PgBouncer Official Configuration Reference (v1.23)
  • PgBouncer Pool Modes — Session vs Transaction vs Statement
  • PostgreSQL 16 — Connection Settings Documentation
  • PostgreSQL 16 — PREPARE Statement Semantics
  • node-postgres pg.Pool — Official Documentation
  • PgBouncer Admin Console (SHOW POOLS / SHOW STATS)

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

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

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