المستوى المطلوب: متوسط. محتاج تكون فاتح Postgres قبل كده وتعرف يعني إيه connection و query. مش محتاج تكون عملت pooling قبل كده.
ليه Postgres بيقول too many connections وإزاي تحلها بـ PgBouncer
لو قاعدة بياناتك بترجّع FATAL: sorry, too many clients already والـ CPU والرام لسه فاضيين، المشكلة مش في حجم السيرفر. المشكلة إنك وصلت لسقف عدد الاتصالات. هنا هتعرف ليه السقف ده موجود، وإزاي PgBouncer بيخلّي 1000 عميل يتشاركوا 20 اتصال فعلي، بملف إعداد تنسخه وتشغّله.
المشكلة باختصار
افتراضيًا، Postgres بيقبل 100 اتصال بس في نفس الوقت. القيمة دي اسمها max_connections. تقدر تشوفها بنفسك:
SHOW max_connections;
-- النتيجة الافتراضية: 100لو عندك 4 نسخ من التطبيق، وكل نسخة فاتحة pool فيه 30 اتصال، يبقى انت طالب 120 اتصال. الرقم ده عدّى الـ 100. أول ما يحصل ضغط، التطبيق بياخد الخطأ بتاع too many clients، والطلبات بتترفض قبل ما توصل للداتابيز أصلًا. وانت بصّيت على لوحة المراقبة لقيت الـ CPU 20% بس. ده اللي بيخلّيك تحتار.
الفكرة ببساطة: شبابيك البنك
تخيّل بنك فيه 4 شبابيك صرف بس. لو دخل 200 عميل، مش هينفع كل واحد ياخد شباك لنفسه. الحل المنطقي: العملاء يقفوا في طابور واحد، وأول ما شباك يفضى، العميل اللي بعده يدخل عليه على طول. الـ 4 شبابيك بيخدموا الـ 200 عميل بالتناوب، وكل عميل بياخد ثواني وبيمشي.
الاتصال بقاعدة البيانات زي الشباك بالظبط. غالي إنك تفتحه، وعدده محدود. بدل ما كل طلب يفتح شباك جديد، خلّيهم يتشاركوا عدد صغير من الاتصالات المفتوحة أصلًا. ده اللي بيعمله PgBouncer: بيقف في النص، يمسك طابور العملاء، ويوزّعهم على عدد ثابت من الاتصالات الجاهزة.
الشرح العلمي: ليه الاتصال غالي في Postgres
دلوقتي بالتفاصيل. Postgres بيستخدم نموذج process-per-connection. يعني كل اتصال جديد بيفتح عملية (process) مستقلة في نظام التشغيل، مش thread خفيف. العملية دي بتاخد ذاكرة خاصة بيها (الـ work_mem والـ buffers المحلية)، وبتحط حمل على الـ scheduler بتاع اللينكس.
توثيق PostgreSQL وويكي المشروع بيوضّحوا إن فتح اتصال جديد فيه تكلفة إعداد ثابتة (مصادقة + تهيئة الـ backend)، وإن رفع max_connections لأرقام كبيرة بيستهلك ذاكرة وبيزوّد الـ context switching حتى لو الاتصالات قاعدة فاضية. الخلاصة: المشكلة مش بس العدد، المشكلة إن كل اتصال كيان تقيل بطبعه. عشان كده الحل الصح مش "زوّد السقف"، الحل "قلّل عدد الاتصالات الفعلية".
قبل وبعد
قبل — اتصال مباشر
- 500 طلب متزامن = محاولة فتح 500 اتصال.