مستوى المقال: متوسط — بيفترض إنك شغّلت 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 بين العمليات.
المثال للمبتدئ: شبّاك التذاكر في السينما
تخيّل سينما فيها 25 شبّاك بيع تذاكر، وفجأة جه 1000 زبون. لو كل زبون مسك شبّاك لنفسه، الشبابيك خلصت بعد 25 زبون والباقي وقف ساعتين بدون أي حركة. لكن لو فيه منظّم واقف بيقول "خد دورك في الطابور، أول ما الشبّاك يفضى تروحله"، نفس الـ 25 شبّاك بيخدموا الـ 1000 في وقت أقل بكتير. ليه؟ لأن المعاملة الفعلية على الشبّاك بتاخد 30 ثانية بس، فالشبّاك الواحد بيخدم 120 زبون في الساعة بدل ما يفضل محجوز لزبون واحد بيقرأ القائمة.
PgBouncer هو المنظّم ده بالظبط. تطبيقاتك (الـ 1000 زبون) بتفتح 1000 connection على PgBouncer (الفتح ده مجاني تقريبًا، استهلاك أقل من 50KB). PgBouncer من ناحيته بيفتح 25 connection بس على PostgreSQL، وبيوزّع الـ queries عليهم بالتتابع. بكدا الـ DB ما بتشوفش الزحام أصلاً.
التعريف العلمي: ليه connection في Postgres مكلف
أول ما عميل يعمل connect() على Postgres بيحصل التتابع ده:
- الـ
postmasterprocess بيستقبل الطلب على البورت 5432. - بيعمل
fork()ليخلق backend process جديد للعميل. - الـ backend بيعمل authentication ضد
pg_hba.conf، يقرأ system catalogs، ويحجز ذاكرة لـwork_memوtemp_buffers. - الإجمالي بياخد بين 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 أوضاع: ، ، و. أهمهم وأكثرهم استخدامًا في الإنتاج هو : العميل بيمسك الـ connection بس طول مدة الـ transaction، وأول ما يحصل أو بترجع للـ pool فورًا. ده بيخلّي 1000 client يتشاركوا 25 connection فعلي طول ما مفيش transaction طويل.