مستوى المقال: متوسط — مناسب لمطور backend عنده خبرة سنة على الأقل مع PostgreSQL ويعرف يقرأ ملف ini.
لو الـ PostgreSQL بتاعك بيرجّع FATAL: sorry, too many clients already كل يوم اتنين الصبح، المشكلة مش في عدد المستخدمين الحقيقي. تطبيقك بيفتح اتصال جديد لكل instance، والـ DB بتقفل الباب عند الـ 100. الحل اسمه PgBouncer في وضع transaction pooling.
PgBouncer Transaction Pooling: من 100 اتصال محدود إلى 2000 اتصال فعّال
المشكلة باختصار
PostgreSQL بيعامل كل connection كـ process مستقل في نظام التشغيل. كل process بياخد حوالي 10MB RAM في حالته الفاضية، وبيتنافس على الـ CPU وقت الـ context switching. عشان كده الإعداد الافتراضي max_connections = 100، وزيادته لـ 500 بتكلّفك ذاكرة فعلية وأداء أسوأ، مش أفضل.
تطبيق Node.js عادي بيفتح pool داخلي بـ 10 اتصالات لكل instance. لو عندك 12 instance خلف load balancer، انت طلبت 120 اتصال، والـ DB كسرت عند الـ 100. النتيجة: 20 طلب/ثانية بيرجعوا 503 من غير سبب واضح في اللوج بتاع التطبيق.
المفهوم بمثال — مكتب استقبال الفندق
تخيل فندق فيه 100 موظف استقبال (دول الـ connections). كل ضيف (request) لازم يقعد مع موظف من ساعة دخوله لساعة خروجه. لو دخل 101 ضيف في نفس اللحظة، الضيف الأخير يستنى أو يطلع.
الحل المنطقي مش زيادة الموظفين لـ 500 (الفندق هيخسر فلوس على رواتبهم وهم 80% فاضيين)، الحل إن الموظفين دول يتعاملوا مع كذا ضيف بالتناوب. الضيف بيكلّم الموظف 30 ثانية، يمشي، الموظف يستقبل اللي بعده. ده بالظبط دور PgBouncer.
التعريف العلمي الدقيق
PgBouncer هو connection pooler خفيف بيشتغل كـ proxy بين تطبيقك والـ PostgreSQL. التطبيق بيفتح اتصال على PgBouncer (رخيص جدًا، حوالي 2KB لكل اتصال)، وPgBouncer بيدير pool حقيقي صغير مع Postgres ويوزّع الـ queries عليه بطريقة multiplexing.
عنده 3 أوضاع pooling لازم تعرفهم قبل ما تقرر:
- session pooling: الاتصال محجوز للـ client من ما يفتح ل ما يقفل. مفيش مكسب فعلي على الـ scaling.
- transaction pooling: الاتصال محجوز فقط طول الـ transaction (من
BEGINلـCOMMIT). ده اللي بيفك الاختناق. - statement pooling: الاتصال محجوز لـ query واحدة. صارم جدًا، بيكسر معظم الـ ORMs.
الإعداد الفعلي — ملف ini قابل للنسخ
ملف pgbouncer.ini الأساسي اللي بيشتغل في إنتاج فعلي: