Laravel 13.35 bổ sung một thay đổi nhỏ nhưng khá tiện cho worker chạy trong container: queue:work --memory có thể nhận phần trăm, chẳng hạn --memory=60%. Khi container đổi từ 512 MB lên 1 GB, đội vận hành không phải tự đổi một con số MB cố định trong mọi file cấu hình.

Tuy vậy, phần trăm này không lấy từ RAM của container và cũng không nới trần bộ nhớ cho PHP. Nó lấy từ memory_limit của PHP CLI, rồi cho Laravel một mốc để worker tự thoát sau khi xử lý job. Nếu nhầm hai lớp này, cấu hình trông có vẻ linh hoạt nhưng vẫn có thể chết vì hard limit hoặc không recycle như mong đợi.

--memory=60% thật sự tính từ đâu?

Tính năng được đưa vào Laravel 13.35.0 ngày 06/10/2026 theo changelog Laravel 13.x và pull request #61753. Bản 13.36.0 hiện tại vẫn giữ cách xử lý này: nếu giá trị kết thúc bằng dấu %, worker đọc ini_get('memory_limit'), đổi shorthand sang byte bằng ini_parse_quantity(), rồi nhân với tỷ lệ đã truyền.

php -r "echo ini_get('memory_limit'), PHP_EOL;"
php artisan queue:work --memory=60%

Với PHP CLI có memory_limit=512M, mốc 60% là 307,2 MB. Với 1G, mốc tương ứng là 614,4 MB. Đây là phép tính từ cấu hình PHP của đúng process worker, không phải 60% tổng RAM máy và cũng không tự biết giới hạn cgroup của container.

Vì vậy, bước kiểm tra đầu tiên không phải nhìn phpinfo() của PHP-FPM. Hãy chạy php --ini và lệnh đọc memory_limit bằng chính binary, file INI và user sẽ khởi động queue worker. Một image có thể dùng INI khác nhau cho FPM và CLI.

Ma trận memory_limit 512M, 1G và -1 với ngưỡng phần trăm cùng kết quả exit của Laravel worker
Ma trận fixture trên Laravel 13.36.0 cho thấy phần trăm bám theo PHP CLI; với -1, worker không có mốc phần trăm hữu hạn.

Fixture cho thấy worker thoát và được khởi động lại ra sao

Mình dựng một ứng dụng Laravel tạm trên Windows 11 Pro build 26300, PHP 8.4.24 và Laravel Framework 13.36.0. Queue dùng SQLite cục bộ. Mỗi job giữ lại một chunk bộ nhớ trong biến static để mô phỏng state còn nằm trong process dài hạn; sau mỗi job, fixture ghi PID, bộ nhớ trước/sau và peak vào JSONL. Đây là môi trường biên để quan sát lifecycle, không phải benchmark production và không chạy trên queue của nghiane.com.

Ma trận gồm memory_limit 512M, 1G và -1; mỗi mức thử 50%, 60% và mốc cố định 192 MB. Với mọi case hữu hạn, worker vượt mốc rồi thoát bằng mã 12, đúng hằng EXIT_MEMORY_LIMIT của Laravel. Một vòng PowerShell đóng vai process monitor, thấy exit 12 thì khởi động worker mới cho đến khi queue trống.

PHP CLI--memoryMốc tínhQuan sát fixture
512M50%256 MBMax 274 MB; chuỗi exit 12, 12, 0
512M60%307,2 MBMax 356 MB; chuỗi exit 12, 12, 0
1G50%512 MBMax 520 MB; chuỗi exit 12, 0
1G60%614,4 MBMax 684 MB; chuỗi exit 12, 0
-150% hoặc 60%Không hữu hạnBốn job đạt 196 MB, hết queue rồi exit 0
512M, 1G hoặc -1192192 MBMax 226 MB; exit 12 rồi worker mới chạy tiếp

Số đo sau job có thể vượt mốc vài chục MB vì Laravel kiểm tra sau khi job hiện tại hoàn tất, không ngắt giữa câu lệnh cấp phát của fixture. Đây cũng là lý do cần khoảng đệm thay vì đặt ngưỡng recycle sát hard limit.

Ngưỡng Laravel không phải hard limit của PHP

PHP Manual định nghĩa memory_limit là lượng bộ nhớ tối đa một script được phép cấp phát; đặt -1 nghĩa là không giới hạn. Khi code vượt hard limit trong lúc một job còn chạy, PHP có thể phát sinh fatal error ngay tại điểm cấp phát. Laravel không thể biến lỗi đó thành một lần recycle êm chỉ bằng --memory.

Ngược lại, --memory là điều kiện dừng ở vòng đời worker. Sau khi job trả quyền điều khiển, Laravel đo mức dùng hiện tại, trả stop reason memory và exit 12 nếu đã chạm mốc. Process monitor mới là thành phần đưa capacity trở lại bằng cách chạy một worker khác.

Sơ đồ phân biệt PHP memory_limit là trần cứng và Laravel memory là ngưỡng recycle worker
Laravel kiểm tra ngưỡng recycle sau job; hard limit của PHP vẫn có thể dừng process trong lúc job đang cấp phát bộ nhớ.

Vì vậy, nếu PHP CLI đặt 512M thì đừng chọn --memory=100% rồi kỳ vọng an toàn. Worker còn cần khoảng trống cho peak tạm thời trong job, framework, extension và dữ liệu chưa được giải phóng. Không có một tỷ lệ “đúng cho mọi hệ thống”; hãy đo workload thật ở staging và chọn buffer theo peak, concurrency cùng giới hạn container.

memory_limit=-1 là negative control quan trọng

tài liệu queue Laravel 13.x ghi rõ phần trăm cần một PHP memory_limit hữu hạn. Code hiện tại đổi -1 thành một giá trị âm; điều kiện dừng chỉ có hiệu lực khi mốc lớn hơn 0. Bởi vậy, hai case -1 với 50% và 60% trong fixture đều xử lý đủ bốn job, giữ cùng một PID và exit 0 khi queue trống.

Nếu môi trường CLI chủ động dùng -1, có hai lựa chọn rõ ràng: đặt lại một hard limit hữu hạn phù hợp với container, hoặc tiếp tục dùng ngưỡng MB cố định cho Laravel. Case --memory=192 trong cùng fixture vẫn exit 12 dù PHP là -1, nên đây là đường rollback thực tế khi chưa thể chuẩn hóa INI.

Supervisor cần %%, còn shell chỉ cần %

Trong shell, lệnh trực tiếp vẫn là php artisan queue:work --memory=60%. Nhưng giá trị command= của Supervisor hỗ trợ Python string expressions như %(program_name)s, theo tài liệu Supervisor 4.3. Dấu phần trăm đơn không hợp lệ trong cơ chế nội suy; cần viết hai dấu để process con nhận một dấu.

[program:laravel-worker]
command=php /var/www/app/artisan queue:work --memory=60%% --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stopwaitsecs=3600

Máy thử Windows không có supervisord, nên phần này chỉ được kiểm tra tĩnh bằng Python 3.12 ConfigParser, cùng kiểu nội suy phần trăm. Chuỗi 60% trả InterpolationSyntaxError; chuỗi 60%% được parse thành lệnh có 60%. Đây không phải bằng chứng về restart thật của Supervisor, vì vòng restart trong lab do PowerShell đảm nhiệm.

Khi rollout trên Linux, hãy dùng công cụ validate của bản Supervisor thực tế, sau đó reread, update và kiểm lệnh process đã spawn. autorestart=true mới khiến worker quay lại sau exit 12. Laravel cũng khuyến nghị stopwaitsecs dài hơn job lâu nhất để Supervisor không kill job đang xử lý.

Khi nào chọn MB, khi nào chọn phần trăm?

Môi trườngLựa chọn hợp lýLý do
VPS cố định, cấu hình lâu dàiMB hoặc %MB dễ audit; % tiện nếu CLI limit vẫn là nguồn sự thật
Container có nhiều size%Mốc tự đi theo PHP limit của từng size, miễn INI được cập nhật cùng resource limit
Nhiều worker chia sẻ hostĐo rồi chọn MB/%Phải tính tổng concurrency, Redis, OS và process khác; % riêng lẻ không giải quyết overcommit
PHP CLI dùng -1MB hoặc finite limit% không tạo được mốc dừng hữu hạn
Supervisor quản lý process% với %%Tránh lỗi nội suy và giữ autorestart

Điểm mấu chốt là phần trăm chỉ giúp đồng bộ một mốc với PHP CLI. Nó không thay thế thiết kế resource request/limit ở orchestrator, không phân bổ RAM giữa nhiều worker và không đo RSS của toàn bộ container.

Đừng dùng recycle để che một memory leak

Worker dài hạn không reboot framework trước mỗi job. Tài liệu Laravel nhắc giải phóng resource nặng sau khi dùng, chẳng hạn hủy image resource. --memory là hàng rào vận hành, không phải lý do bỏ qua việc đóng stream, giải phóng handle hoặc tìm collection giữ tham chiếu ngoài ý muốn.

Nên kết hợp ba điều kiện bổ sung: --max-jobs để giới hạn số job mỗi process, --max-time để tránh một PID sống quá lâu, và cleanup ngay trong job. Bài Laravel 13.35 chạy scheduler trên một server cho thấy cùng một nguyên tắc: thay đổi nhỏ ở CLI chỉ an toàn khi lifecycle process và cơ chế failover được hiểu rõ.

Trước khi nâng framework trên hệ thống thật, nên pin dependency và chạy regression như checklist Laravel với PHP 8.6 RC3. Nếu còn ở Laravel 12, bài kiểm tra đường nâng cấp Laravel 12 giúp tách blocker package khỏi thay đổi cấu hình worker.

Checklist rollout không làm rơi job

  1. Pin Laravel từ 13.35 trở lên và xác nhận changelog của đúng version sẽ deploy.
  2. Đọc php --ini cùng ini_get('memory_limit') bằng đúng PHP CLI của worker.
  3. Chạy fixture ở staging với positive control hữu hạn, negative control -1 và một mốc MB để rollback.
  4. Chọn buffer dựa trên peak sau job; không đặt mốc sát hard limit chỉ vì phép tính tròn.
  5. Nếu dùng Supervisor, viết 60%%, validate file, bật autorestart và đặt stopwaitsecs dài hơn job lâu nhất.
  6. Quan sát exit code, PID mới, queue depth, failed job và thời gian khôi phục capacity.
  7. Giữ --max-jobs, --max-time và cleanup resource như các lớp bổ sung.

Phần trăm đáng dùng nhất khi PHP CLI limit đã được quản lý theo size container và process monitor hoạt động ổn định. Nếu hai điều đó chưa chắc chắn, một ngưỡng MB đã đo, có rollback và có cảnh báo rõ ràng vẫn dễ vận hành hơn.