المستوى: متوسط إلى محترف — هذا المقال يفترض إنك بتشتغل على PostgreSQL في إنتاج وعندك خلفية أساسية بإعداد قاعدة البيانات وقراءة logs.
لو السيرفر بيتجمّد لما الاتصالات يوصلوا لـ 300 ومش قادر يخدم زبون جديد، PostgreSQL مش بطيء — كل اتصال بياكل بين 8 و 12MB رام، وفتح اتصال جديد بياخد 3 إلى 5ms في الـ handshake والـ authentication. PgBouncer بيخدم 10,000 عميل من 25 إلى 100 اتصال DB فقط، بدون لمس كود التطبيق.
PgBouncer: تخفيف ضغط الاتصالات على PostgreSQL بدون توسيع الهاردوير
المشكلة باختصار
PostgreSQL بيستخدم process مستقل لكل اتصال — مش thread خفيف. process معناها fork() جديد على مستوى الـ kernel، كومة ذاكرة منفصلة، file descriptors خاصة، وسجل في scheduler الـ Linux. على متوسط workload، الاتصال الخامل بياكل 8 إلى 12MB رام بدون ما ينفّذ أي query.
الإعداد الافتراضي max_connections = 100 مش رقم عشوائي — هو رقم معايرة محافظ. لما العدد ده يتعدّى، أي طلب جديد بيرجع بـ FATAL: sorry, too many clients already، وأسوأ من كده، الـ context switching بين 1000 process بياكل CPU في scheduling مش في تنفيذ queries.
الحل اللي معظم المطورين بيلجأوا له هو رفع max_connections لـ 1000 — وده غلط مكلف. عند 1000 اتصال خامل، PostgreSQL بيستهلك 10GB رام بدون أي طلب فعلي، والـ shared_buffers بتفقد مساحتها، والأداء بينهار قبل ما يتحسّن.
تخيّل الموضوع كأنه سوبر ماركت
قبل ما ندخل في التعريف العلمي، خد المثال ده عشان توصل الفكرة بسرعة:
متصوّر سوبر ماركت كبير فيه 100 ماكينة دفع. لو كل زبون يدخل المحل ياخد ماكينة خاصة بيه ويفضل ماسكها طول ما هو بيتسوّق ويتمشى ويفكّر — أول 100 زبون بس هيقدروا يدخلوا المحل، والباقي هيقفوا برة ينتظروا. ده اللي بيحصل في PostgreSQL بدون pooler.
دلوقتي تخيّل نفس المحل بنظام طوابير ذكي: الزبون يدخل، يتسوّق وهو حر، ولما يجي يدفع نظام الطابور بيوزّعه على أول ماكينة فاضية. الماكينة بتخدمه في 30 ثانية وترجع جاهزة لزبون تاني. نفس الـ 100 ماكينة دلوقتي تقدر تخدم آلاف الزبائن في الساعة، لأن وقت الإمساك الفعلي للماكينة قصير جداً مقارنة بوقت التسوّق.
هذا بالظبط دور Connection Pooling: طبقة وسيطة بتمسك مجموعة محدودة من الاتصالات الحقيقية مع PostgreSQL، وبتأجّر الاتصال للعميل في اللحظة اللي محتاج فيها يبعت query فعلي، وبترجعه للـ pool فور انتهائه. العميل ما يحسّش بفرق، لكن قاعدة البيانات بتشتغل على عدد processes أقل بكثير.
التعريف العلمي الدقيق
PgBouncer هو middleware خفيف (binary أقل من 1MB، single-threaded event-driven باستخدام libevent) بيقف بين التطبيق وPostgreSQL على بورت مختلف (افتراضياً 6432 بدل 5432). من جهة العميل بيقبل آلاف الاتصالات لأن كل اتصال معاه عبارة عن file descriptor + buffer صغير. من جهة الـ DB بيمسك pool محدود من اتصالات حقيقية ومعاد استخدامها.
التطبيق بيتصل بـ PgBouncer كأنه بيتصل بـ PostgreSQL مباشرة — نفس wire protocol، نفس الكود، نفس الـ driver. الفرق الوحيد: البورت اللي بيتصل عليه.