Upload File Lớn Trong PHP/Laravel: Fix Lỗi 413 Và Out Of Memory
Bài này kể lại một case thực tế: QC upload một file CSV 9.99MB vào màn hình import, lần đầu nhận lỗi 413 The POST data is too large., sửa xong thì chuyển sang lỗi 500 với response trống. Hai lỗi nhìn đơn giản, nhưng nguyên nhân nằm ở bốn tầng khác nhau: nginx, PHP-FPM, Laravel, và chính cách code đọc file.
Đi qua từng bước debug trong case này, bạn sẽ nắm được:
- Một request upload đi qua những "cửa" giới hạn nào, và cửa nào trả lỗi gì.
- Cách chỉnh
upload_max_filesize,post_max_size,memory_limitđúng chỗ (và cái bẫy của.user.ini). - Vì sao tăng RAM không phải là cách sửa, và cách đo bộ nhớ thật của một đoạn code PHP.
- Cách refactor code đọc CSV sang streaming bằng generator: RAM của reader giảm từ ~930MB xuống 16MB.
Tổng Quan: Request Upload Đi Qua Những Tầng Nào?
Trước khi debug, cần có bức tranh toàn cảnh. Với stack phổ biến nginx → PHP-FPM → Laravel, một request multipart/form-data phải đi qua lần lượt các giới hạn sau:
| Tầng | Cấu hình | Giá trị mặc định | Khi vượt giới hạn |
|---|---|---|---|
| nginx | client_max_body_size |
1m |
nginx trả 413 (HTML của nginx) |
| PHP-FPM | post_max_size |
8M |
PHP bỏ trống $_POST/$_FILES, Laravel trả 413 "The POST data is too large." |
| PHP-FPM | upload_max_filesize |
2M |
File bị đánh dấu lỗi UPLOAD_ERR_INI_SIZE, Laravel validation fail (422) |
| Laravel | Rule max:10240 (đơn vị KB) |
Tùy code | 422 kèm message validation |
| PHP runtime | memory_limit |
128M |
Fatal error, response thường là 500 trống |
| OS | RAM + swap | Tùy server | OOM killer kill process, 502 |
Điểm quan trọng: mỗi tầng trả một mã lỗi khác nhau. Nhìn vào mã lỗi và nội dung response là đoán được request đã "chết" ở tầng nào:
413với body JSONThe POST data is too large.→ PHP (post_max_size), không phải nginx.413với trang HTML413 Request Entity Too Large→ nginx.500với body rỗng → PHP fatal error (thường là hết memory hoặc timeout), Laravel không kịp render JSON lỗi.
Bước 1: Tái Hiện Lỗi Bằng curl Thay Vì Click Trên UI
Đừng debug bằng cách nhờ QC upload lại. Hãy tạo một file cùng kích thước và bắn thẳng vào API:
# Tạo file ngẫu nhiên ~9.99MB
head -c 10475274 /dev/urandom > f999.csv
# POST vào một route không tồn tại để chỉ test tầng giới hạn body
curl -s -o /dev/null -w "HTTP %{http_code}\n" \
-X POST -H 'Accept: application/json' \
-F "file=@f999.csv" \
https://api.example.com/api/v1/ping-upload-test
Giải thích:
head -c N /dev/urandom: tạo file đúng N byte, không cần nội dung hợp lệ vì ta chỉ test giới hạn kích thước.- Gửi vào route không tồn tại là một mẹo hay: nếu body qua được mọi giới hạn, Laravel sẽ trả
404. Nếu bị chặn ở PHP, ta nhận413. Không cần auth, không ghi dữ liệu gì. -H 'Accept: application/json'để Laravel trả JSON thay vì redirect.
Kết quả lần đầu: HTTP 413. Đã tái hiện được lỗi.
Yêu cầu đăng nhập
Vui lòng đăng nhập để truy cập nội dung này
Additional Resources
Course Guide
Comprehensive PDF guide with examples
GitHub Repository
Example code for all lessons
Discussion
Have a question about this lesson? Post it here and get answers from instructors and peers.
