Am găsit de ce PHP rămâne blocat la un upload de 8 MB

这篇内容暂时只有 Română 版本,以下是原文。

Un client urca fișiere prin formular pe un server cu PHP 8.2, nginx 1.24 și PHP-FPM pe socket Unix. Fișierele mici mergeau, cele peste 8 MB dădeau 502 după exact 60 de secunde. Nu era load, era un singur utilizator. Am păstrat logurile de acces și am cronometrat: cererea ajungea în nginx, trecea la FPM, apoi nimic până la timeout.

Am început cu ce se vede, nu cu presupuneri:

php -i | grep -E 'upload_max_filesize|post_max_size|max_execution_time|max_input_time'
nginx -T | grep -E 'client_max_body_size|fastcgi_read_timeout|proxy_read_timeout'

Valorile erau amestecate: upload_max_filesize=2M, post_max_size=8M, client_max_body_size implicit 1M în nginx, iar fastcgi_read_timeout tot 60s. Am pus limite coerente și am adăugat un buffer explicit:

client_max_body_size 32m;
client_body_buffer_size 256k;
fastcgi_read_timeout 180s;

În PHP:

upload_max_filesize = 32M
post_max_size = 34M
max_execution_time = 120
max_input_time = 120

Dar adevărata blocare era în aplicație: citea $_FILES și apoi muta fișierul cu move_uploaded_file pe un disc NFS, fără timeout. Am scris fișierul întâi local, apoi l-am copiat asincron:

$tmp = tempnam(sys_get_temp_dir(), 'up_');
if (!move_uploaded_file($_FILES['f']['tmp_name'], $tmp)) {
    throw new RuntimeException('upload eșuat');
}
// răspuns imediat, copierea pe NFS o face un worker separat

Mecanismul e simplu: nginx bufferează body-ul pe disc dacă depășește client_body_buffer_size, iar FPM așteaptă să citească tot înainte de a rula scriptul. Dacă aplicația mai și blochează un worker pe I/O lent, coada crește și pentru cererile mici. Am redus request_terminate_timeout la 150s ca să nu acumulez procese moarte, cu prețul că un upload foarte lent e tăiat mai devreme.

Unde se strică: dacă folosești client_body_in_file_only clean, nginx șterge body-ul după ce scriptul îl citește, deci nu poți relua upload-ul. Pe NFS, rename nu e atomic între mount-uri. Și dacă pui post_max_size mai mic decât upload_max_filesize, PHP ignoră fișierul fără eroare vizibilă, doar $_FILES e gol.

Voi cum tratați upload-urile mari când storage-ul final nu e local?

0
赞同度 100% · 0 条评论 · 4 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

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