FATAL: sorry, too many clients already تحت الضغط، المشكلة مش إن PostgreSQL ضعيف. المشكلة إنه فعلاً مش مصمم يتعامل مع آلاف الـ connections مباشرةً. PgBouncer بيحل الموضوع بملف config واحد صغير و container واحد إضافي، وبيوفّرلك حرفياً 80% من ذاكرة قاعدة البيانات.
المشكلة باختصار
في PostgreSQL، كل connection = process منفصل. يعني لمّا 500 عميل يفتحوا connection، هتلاقي 500 process شغّال على السيرفر. كل واحد فيهم بيحجز في المتوسط من 5 إلى 15 ميجابايت RAM، وده من غير حساب الـ work_mem أو الـ temp buffers. على سيرفر بـ 8GB RAM، الحسابات بتوقف من أول 400 connection تقريباً.
الـ default limit في PostgreSQL هو max_connections = 100 لسبب وجيه. فرق كتير بترفعه لـ 500 أو 1000 ظنًا ان ده بيحل المشكلة، والنتيجة: سيرفر بيوقع بدل ما يستجيب.
ليه Connection Pool أصلاً ضروري
خلّيني أشرح بمثال بسيط قبل ما ندخل في التقنية. تخيّل مستشفى فيه 20 طبيب. لو كل مريض بيدخل ياخد طبيب لنفسه ويقعده معاه طول اليوم حتى لو هيعمل كشف 5 دقايق، المستشفى هيقدر يخدم 20 مريض بس. لكن لو في موظف استقبال بيوزّع المرضى على الأطباء الفاضيين بالدور، نفس الـ 20 طبيب يقدروا يخدموا 500 مريض في اليوم.
PgBouncer هو موظف الاستقبال ده. بيقعد قدام PostgreSQL ويستقبل آلاف الـ client connections، وبيوزّعهم على pool صغير من server connections فعلية. العميل مش بياخد server connection لنفسه طول العمر، بياخدها بس ثواني لمّا محتاجها فعلياً لتنفيذ query.
الـ 3 Pool Modes — الاختيار الغلط هنا بيكسرلك الـ app
PgBouncer فيه 3 أوضاع pooling، والفرق بينهم مش تفصيل — هو اللي بيحدد شكل الكود اللي هتكتبه.
- Session pooling: العميل بياخد server connection من أول ما يعمل connect لحد ما يعمل disconnect. أبسط وضع، ومش بيوفّر حاجة تقريبًا لأنك بتعمل نفس ما بيحصل بدون PgBouncer.
- Transaction pooling: العميل بياخد server connection بس طول مدة الـ transaction، وبمجرد ما يعمل COMMIT أو ROLLBACK، الـ connection بترجع للـ pool. ده الوضع الافتراضي اللي بيستخدمه معظم الناس، وبيحقق أكبر توفير.
- Statement pooling: كل query لوحده بياخد connection. أقصى توفير ممكن، بس بيكسر أي حاجة فيها transaction.
الوضع المناسب لـ 95% من الحالات هو transaction pooling. بس فيه قيود لازم تعرفها قبل ما تفعّله، هنتكلم عنها تحت.
تثبيت شغّال في 4 دقايق — docker-compose
الأسهل والأكثر احترافية: شغّله كـ sidecar container جنب قاعدة البيانات.
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
command: -c max_connections=50
pgbouncer:
image: edoburu/pgbouncer:1.22.1
environment:
DATABASE_URL: "postgres://postgres:secret@postgres:5432/app"
POOL_MODE: transaction
MAX_CLIENT_CONN: 1000
DEFAULT_POOL_SIZE: 25
RESERVE_POOL_SIZE: 5
SERVER_IDLE_TIMEOUT: 60
ports:
- "6432:5432"
depends_on:
- postgres