خادم Ubuntu 22.04، PHP 8.2 مع FPM، MySQL 8.0، وحركة تقارب 1200 طلب في الدقيقة. بعد تفعيل الاتصالات الدائمة في pool واحد، ارتفع عدد الاتصالات على قاعدة البيانات من 40 إلى 180 خلال ساعة، وبدأ الخادم يرفض الاتصالات الجديدة بـ Too many connections. الظاهرة لم تظهر في staging لأن عدد العمال هناك 4 فقط.
الخطوة الأولى كانت تثبيت العدد الفعلي بدل التخمين:
mysql -e "SHOW STATUS LIKE 'Threads_connected'; SHOW STATUS LIKE 'Threads_created';"
watch -n 2 "mysql -e \"SHOW PROCESSLIST\" | grep -c php"
ثم راجعت إعدادات pool. المشكلة كانت أن pm.max_children عندي 32، لكن pm.max_requests كان صفرًا، أي أن العمال لا يُعاد إنشاؤها. مع pdo_mysql وPDO::ATTR_PERSISTENT => true، كل عامل يحتفظ باتصاله الخاص، فيصبح السقف الحقيقي هو عدد العمال مضروبًا في عدد قواعد البيانات التي يفتحها كل عامل. عندي عاملان لكل طلب في بعض المسارات، فصار الرقم 64 اتصالًا دائمًا لا يُغلق، ثم أضف عمالًا احتياطيين فبلغ 180.
ما فعلته هو تقييد الاتصالات الدائمة بحد واضح بدل إلغائها:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_TIMEOUT => 3,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET SESSION wait_timeout=60',
]);
وفي pool:
pm = dynamic
pm.max_children = 24
pm.max_requests = 500
pm.status_path = /status
السبب في رأيي أن الاتصال الدائم يوفّر مصافحة TLS ومصادقة عند كل طلب، وهذا مكسب حقيقي على شبكة بعيدة. لكن الثمن أن الحالة تبقى على الخادم: معاملات جلسة، LAST_INSERT_ID، وTEMPORARY TABLE قد تتسرب من طلب إلى آخر إن لم تُنظّف. لهذا ضبطت wait_timeout من جهة MySQL ليكون أقل من مهلة الخادم، حتى لا يجد العامل اتصالًا ميتًا.
ما لم يكن واضحًا لي: هل max_requests يعيد الاتصال الدائم أصلًا عند إعادة إنشاء العامل؟ في تجربتي نعم، لكن ليس فورًا، وبعض العمال بقوا حتى انتهاء المهلة. لذلك اعتمدت مراقبة Threads_created لا Threads_connected وحدها، لأن الأولى تكشف إن كنا نعيد فتح اتصالات أكثر مما نظن. أيضًا عند تفعيل persistent مع أكثر من DSN، كل DSN له اتصال منفصل، وهذا ما ضاعف الرقم عندي دون أن أنتبه.
الحد الذي لا ينفع فيه هذا الحل: لو كان كل طلب يفتح اتصالًا لقاعدة مختلفة حسب المستأجر، فالاتصالات الدائمة تتحول إلى ذاكرة مخبأة بلا سقف. جربت persistent مع 200 مستأجر فانهار العدد خلال دقائق، وعدت إلى اتصال عادي مع pool صغير.
سؤالي: من جرّب pm.max_requests مع persistent في FPM، هل لاحظ أن العمال تُعاد دورتها فعلًا أم أن الاتصال يبقى معلقًا حتى wait_timeout؟


0 条评论
还没有评论,写下第一条。