أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالمناهج والباقات
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • المناهج والباقات
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالمناهجالمدونةالدخول
DevOps بالعربي

PgBouncer للمتوسط: ليه Postgres بيقول too many connections وإزاي تحلها

متوسط٢٦ يونيو ٢٠٢٦6 دقائق قراءة
PgBouncer للمتوسط: ليه Postgres بيقول too many connections وإزاي تحلها

المستوى المطلوب: متوسط. محتاج تكون فاتح Postgres قبل كده وتعرف يعني إيه connection و query. مش محتاج تكون عملت pooling قبل كده.

ليه Postgres بيقول too many connections وإزاي تحلها بـ PgBouncer

لو قاعدة بياناتك بترجّع FATAL: sorry, too many clients already والـ CPU والرام لسه فاضيين، المشكلة مش في حجم السيرفر. المشكلة إنك وصلت لسقف عدد الاتصالات. هنا هتعرف ليه السقف ده موجود، وإزاي PgBouncer بيخلّي 1000 عميل يتشاركوا 20 اتصال فعلي، بملف إعداد تنسخه وتشغّله.

المشكلة باختصار

افتراضيًا، Postgres بيقبل 100 اتصال بس في نفس الوقت. القيمة دي اسمها max_connections. تقدر تشوفها بنفسك:

SQL
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 اتصال.
  • السقف 100 اتصال، والباقي بيترفض بـ too many clients already.
  • ذاكرة مهدورة في عمليات قاعدة فاضية.

بعد — PgBouncer

  • 500 طلب بيقفوا في طابور عند PgBouncer.
  • 20 اتصال فعلي بس على Postgres.
  • صفر رفض، والطلب بيستنى أجزاء من الثانية. زمن الاستجابة ثابت.

الحل: ركّب PgBouncer وحطّه في النص

PgBouncer برنامج خفيف جدًا. بيشتغل كأنه Postgres مزيّف: التطبيق بيتكلم معاه على بورت 6432 وهو فاكره الداتابيز، وPgBouncer بيتكلم مع Postgres الحقيقي على 5432.

ده ملف pgbouncer.ini كامل قابل للنسخ:

[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20

بعد ما تجهّز ملف userlist.txt بالمستخدم والهاش بتاعه، شغّل الخدمة وبدّل بورت التطبيق:

Bash
sudo systemctl restart pgbouncer

# بدل ما التطبيق يتصل على 5432، خلّيه يتصل على 6432
psql "host=127.0.0.1 port=6432 dbname=mydb user=appuser"

التغيير في التطبيق سطر واحد: بدّل البورت من 5432 لـ 6432 في الـ connection string. خلاص. بقيت ماشي على PgBouncer.

أهم سطر: pool_mode

الـ pool_mode هو اللي بيحدد إمتى الاتصال الفعلي يرجع للـ pool عشان غيره يستخدمه. فيه تلات أوضاع، بس اتنين هما المهمين:

  • session: الاتصال الفعلي بيفضل محجوز للعميل طول ما هو فاتح. آمن جدًا لكن بيوفّر أقل.
  • transaction: الاتصال بيرجع للـ pool بعد كل transaction (كل COMMIT). ده اللي بيوصلك لنسبة المشاركة العالية: 1000 عميل على 20 اتصال.

الافتراض هنا إن تطبيقك بيعمل transactions قصيرة (web requests عادية). لو ده وضعك، transaction هو اختيارك.

الأرقام

سيناريو واقعي: خدمة بـ 500 طلب متزامن وقت الذروة، 4 نسخ تطبيق، max_connections = 100. قبل PgBouncer كنا بنشوف موجات من too many clients ساعة الضغط، وبنضطر نرفع max_connections لـ 300، اللي زوّد استهلاك ذاكرة Postgres بحوالي 700MB على الفاضي (تقدير مبني على 2 إلى 3MB لكل backend خامل، رقم تقريبي بيختلف حسب الإعداد).

بعد PgBouncer بـ default_pool_size = 20 و pool_mode = transaction: عدد الاتصالات الفعلية على Postgres نزل من 300 لـ 20. الرفض اختفى. الزيادة في زمن الاستجابة من القفزة الإضافية كانت أقل من ميلي ثانية في الحالة العادية، لأن PgBouncer بيشتغل على نفس الشبكة المحلية.

الـ trade-offs، مفيش حاجة ببلاش

اختيار transaction بيكسب مشاركة عالية، بس بيخسر حاجات على مستوى الـ session:

  • الـ prepared statements: في النسخ القديمة كان لازم تقفلها على مستوى السيرفر. PgBouncer من إصدار 1.21 بقى بيدعمها في وضع transaction عبر max_prepared_statements، فلو نسختك أقدم خد بالك.
  • أوامر الـ session: أي SET أو متغير على مستوى الاتصال ممكن يتسرّب لعميل تاني، لأن الاتصال بيتنقل. متعتمدش على حالة محفوظة في الاتصال نفسه.
  • LISTEN/NOTIFY والـ advisory locks اللي بتمتد عبر transactions: مش بتشتغل صح في وضع transaction. دي محتاجة session أو اتصال مباشر.
  • نقطة فشل واحدة: PgBouncer بقى في طريق كل طلب. شغّله مع إعداد عالي التوفّر، مش instance واحدة وحيدة.

متى لا تستخدم PgBouncer

مش كل مشروع محتاجه. سيبه لو:

  • عدد اتصالاتك القصوى أقل بكتير من max_connections وثابت، يبقى مفيش مشكلة تتحل أصلًا.
  • تطبيقك معتمد بشكل أساسي على LISTEN/NOTIFY أو حالة session مستمرة، التعقيد مش هيستاهل.
  • بتستخدم خدمة مُدارة عندها pooler جاهز (زي RDS Proxy أو الـ pooler المدمج في Supabase)، استخدم اللي قدامك بدل ما تركّب طبقة تانية.

الخطوة التالية

افتح الداتابيز وشغّل SHOW max_connections;، وبعدها قيس أعلى عدد اتصالات فعلية وصلتله وقت الذروة من pg_stat_activity. لو الرقم بيقرّب من السقف أو بيعدّيه، ركّب PgBouncer بإعداد pool_mode = transaction و default_pool_size = 20 على بيئة staging الأول، بدّل بورت التطبيق لـ 6432، وقارن عدد الاتصالات قبل وبعد. لو نزل زي ما فوق، انقله للإنتاج.

المصادر

  • توثيق PgBouncer الرسمي، الإعداد: pgbouncer.org/config.html
  • توثيق PgBouncer، أوضاع الـ pooling والمميزات: pgbouncer.org/features.html
  • دعم prepared statements في وضع transaction، changelog 1.21: pgbouncer.org/changelog.html
  • توثيق PostgreSQL، إعدادات الاتصال و max_connections: postgresql.org/docs/current/runtime-config-connection.html
  • ويكي PostgreSQL، عدد اتصالات قاعدة البيانات: wiki.postgresql.org/wiki/Number_Of_Database_Connections

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة