PHP 8.2 وملفات الرفع الكبيرة: أين تختفي الذاكرة قبل move_uploaded_file
这篇内容暂时只有 العربية 版本,以下是原文。
خادم Ubuntu 22.04، PHP 8.2 مع FPM، ورفع ملفات PDF بحجم 60–120 ميغابايت عبر نموذج عادي. المشكلة لم تكن فشل الرفع، بل أن كل عملية رفع تترك ذاكرة FPM مرتفعة، وبعد عشرين رفعًا متتاليًا يبدأ العامل بإعادة التشغيل. حجم الملفات الكلي في الطلب الواحد قد يصل 300 ميغابايت.
أول ما فعلته هو قياس ما يحدث قبل اللمس: سجلت memory_get_peak_usage داخل سكربت الرفع وقرأت memory_limit وpost_max_size وupload_max_filesize من phpinfo، ثم راقبت RSS للعامل عبر ps. الفرق بين ما يقوله PHP وما يقوله نظام التشغيل كان هو المفتاح: السكربت يقول 8 ميغابايت والنظام يقول 260.
الخطوة العملية كانت في إعداد FPM وليس في الكود:
; php-fpm.d/www.conf
pm = dynamic
pm.max_children = 12
pm.max_requests = 80
request_terminate_timeout = 120s
; php.ini
upload_max_filesize = 128M
post_max_size = 140M
memory_limit = 256M
ثم في الكود توقفت عن استخدام file_get_contents على الملف المرفوع، واستخدمت المسار المؤقت مباشرة:
$tmp = $_FILES['doc']['tmp_name'];
$hash = hash_file('sha256', $tmp);
$dest = '/srv/uploads/' . $hash . '.pdf';
if (!move_uploaded_file($tmp, $dest)) {
throw new RuntimeException('move failed');
}
السبب أن PHP يضع الملف المرفوع على القرص في tmp ثم يعطيك مسارًا؛ أي قراءة كاملة له في متغير تعني نسخة في ذاكرة العملية. hash_file وmove_uploaded_file يعملان على المسار، فلا يمر المحتوى عبر ذاكرة PHP. الثمن أنك تفقد إمكانية المعالجة أثناء التدفق، وتحتاج مساحة قرص مؤقتة ومراقبة تنظيفها.
الحدود: هذا لا يحل مشكلة إذا كنت تقرأ الملف كاملًا لتحليله في الذاكرة، ولا إذا كان reverse proxy يخزن الطلب كاملًا في الذاكرة قبل FPM. جربت مرة nginx مع client_body_buffer_size كبير، فكان الطلب يستهلك ذاكرة الوكيل قبل أن يصل PHP أصلًا. كذلك pm.max_requests يخفي التسرب ولا يعالجه؛ إن كان هناك متغير عام يحتفظ بالمحتوى فستعود المشكلة. ومن الشروط التي أنساها كثيرًا: file_uploads وupload_tmp_dir على قرص لا يمتلئ، وإلا صار الفشل صامتًا.
السؤال الذي لم أحسمه: هل تعتمد على فحص checksum بعد الرفع، أم تتحقق من النوع والحجم أثناء التدفق عبر nginx أو وحدة أخرى قبل أن يصل الملف إلى PHP؟


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