Laravel 13.31 bổ sung các job có thể bị ngắt (interruptible jobs), cho phép các queue worker bắt được tín hiệu SIGTERM được gửi trong quá trình triển khai (deployment) và tắt máy một cách êm đẹp thay vì hủy bỏ tác vụ khi đang chạy dở. Các đội ngũ chạy các job dài—như xử lý hàng chục nghìn dòng dữ liệu, gửi email hàng loạt hoặc tạo báo cáo—sẽ được hưởng lợi vì dữ liệu luôn duy trì tính nhất quán khi một bản phát hành mới được triển khai.

Tại sao các đợt triển khai thường làm hỏng các job

Khi một phiên bản mới được đưa lên, các trình quản lý tiến trình như Supervisor, Docker và Kubernetes sẽ yêu cầu các worker hiện tại dừng lại bằng cách gửi SIGTERM, một yêu cầu dừng lịch sự. Hầu hết các worker sẽ đợi job hiện tại hoàn thành trước khi thoát. Vấn đề nảy sinh khi trình quản lý chỉ cho phép vài giây để tuân thủ. Nếu một job cần nhiều phút để xong, trình quản lý sẽ leo thang lên SIGKILL, giết chết tiến trình ngay lập tức. Job khi đó không bao giờ đạt đến trạng thái nhất quán, để lại các bản ghi được cập nhật một phần, các tệp tin mồ côi hoặc công việc bị trùng lặp.

Câu trả lời của Laravel: sự ngắt quãng mang tính hợp tác

Laravel 13.31 giới thiệu một contract tên là Interruptible mà một class job có thể triển khai để nhận biết được yêu cầu dừng. Khi SIGTERM đến, Laravel sẽ gọi phương thức interrupted() của job đó. Framework không tự động dừng job; bản thân job phải tự kiểm tra một flag mà Laravel đã thiết lập và tự thoát. Mô hình hợp tác này cho phép các nhà phát triển hoàn tất vòng lặp hiện tại, hoàn tác (roll back) các thay đổi chưa hoàn tất, hoặc ghi lại một điểm kiểm soát (checkpoint) trước khi dừng lại.

Cách để biến một job thành interruptible

  1. Triển khai contract – thêm implements Interruptible vào định nghĩa class job.
  2. Kiểm tra flag bên trong vòng lặp xử lý – chèn một bước kiểm tra sau mỗi vòng lặp để xem job có nên dừng lại hay không.
  3. Thực hiện dọn dẹp – trong phương thức interrupted(), hãy lưu lại bất kỳ trạng thái nào cho phép job có thể tiếp tục sau đó, hoặc ghi log việc bị ngắt quãng để phân tích sau này.

Cái bẫy lớn nhất: đừng sử dụng --once

Chạy queue worker với lệnh php artisan queue:work --once sẽ vô hiệu hóa hoàn toàn việc xử lý tín hiệu. Worker sẽ xử lý một job duy nhất rồi thoát, nhưng nó không bao giờ đăng ký trình xử lý SIGTERM. Do đó, bất kỳ job interruptible nào cũng sẽ bỏ qua yêu cầu dừng và bị giết chết giữa chừng. Mô hình này thường xuất hiện trong các container chạy bằng cron và các đợt triển khai một lần (one-shot deployments). Hãy khắc phục bằng cách chạy chế độ daemon (php artisan queue:work) bất cứ khi nào bạn cần sử dụng các job interruptible.

Theo dõi các lần bị ngắt quãng

Laravel kích hoạt hai sự kiện mà bạn có thể hook vào:

  • WorkerInterrupted – được phát ra bất cứ khi nào có bất kỳ worker nào nhận được SIGTERM. Việc ghi log sự kiện này giúp bạn có cái nhìn tổng quan về tần suất các đợt triển khai làm gián đoạn các worker.
  • JobInterrupted – chỉ được phát ra nếu job hiện tại có triển khai contract Interruptible. Hãy sử dụng sự kiện này để kích hoạt việc dọn dẹp đặc thù cho job, chẳng hạn như xóa các tệp tạm thời hoặc cập nhật dấu mốc "dòng cuối cùng đã xử lý".

Việc lắng nghe các sự kiện này cho phép các đội ngũ xây dựng dashboard để hiển thị tác động của việc triển khai và phát hiện các job thường xuyên bị cắt ngang.

Cải tiến báo cáo kích thước hàng đợi

Bản phát hành này bổ sung phương thức totalSize() vào queue manager, trả về tổng số job đang chờ xử lý trên tất cả các hàng đợi. Giá trị này đáng tin cậy đối với các driver dựa trên database và Redis, vốn báo cáo số lượng thực tế. Các driver như SQS, Sync và Beanstalkd vẫn trả về giá trị bằng không vì chúng không cung cấp số lượng thông qua API của Laravel. Các đội ngũ đang sử dụng SQS nên tiếp tục dựa vào các chỉ số CloudWatch để kiểm tra độ sâu của hàng đợi.

Ý nghĩa đối với các thiết lập khác nhau

  • Docker/Kubernetes – khoảng thời gian tắt máy êm đẹp (graceful shutdown) giờ đây có thể được sử dụng hiệu quả. Hãy kéo dài thời gian chờ tắt máy (termination grace period) để các job có vài giây nhận biết flag và thoát một cách an toàn.
  • Supervisor – thiết lập stopwaitsecs cao hơn thời gian bạn dự kiến một job sẽ hoàn thành sau khi nhận được flag ngắt.
  • Các job cũ (Legacy jobs) – xem xét bất kỳ job nào chạy lâu hơn thời gian timeout của trình quản lý và nếu có thể, hãy chuyển chúng sang dạng interruptible.

Quan điểm ngược lại: sự phức tạp tăng thêm

Tính năng này không thay thế cho việc cấu hình timeout đúng cách. Nếu vòng lặp của một job không bao giờ kiểm tra flag ngắt, trình quản lý vẫn sẽ gửi SIGKILL. Các đội ngũ phải kiểm tra (audit) các luồng mã chạy lâu và chèn các bước kiểm tra tại các điểm ngắt logic. Một số nhà phát triển có thể thấy việc thêm mã lặp lại (boilerplate)—triển khai một contract và thêm các bước kiểm tra flag—là không cần thiết đối với các job ngắn vốn đã hoàn thành trong cửa sổ thời gian tắt máy.

Danh sách kiểm tra hành động

  1. Quét các kịch bản triển khai để tìm cờ --once và thay thế nó bằng chế độ daemon ở những nơi sử dụng các tác vụ có thể bị ngắt (interruptible jobs).
  2. Xác định các tác vụ chạy lâu nhất (những tác vụ xử lý hàng chục nghìn dòng hoặc chạy trong vài phút) và thêm contract Interruptible.
  3. Chèn một bước kiểm tra cờ vào bên trong mỗi vòng lặp, và chuyển bất kỳ mã hoàn tất cần thiết nào vào phương thức interrupted().
  4. Gắn các listener vào WorkerInterrupted và JobInterrupted để thu thập các chỉ số về tần suất việc triển khai ảnh hưởng đến quá trình xử lý.
  5. Đối với các hàng đợi Redis hoặc cơ sở dữ liệu, hãy sử dụng totalSize() để theo dõi backlog; đối với SQS, hãy duy trì các cảnh báo CloudWatch.

Hãy coi việc dừng dịch vụ do triển khai là một bước có sự phối hợp thay vì là một lệnh dừng đột ngột. Laravel 13.31 giúp giữ cho dữ liệu nhất quán và giảm bớt những rắc rối về vận hành do các tác vụ chưa hoàn thành gây ra. Sự đánh đổi là trách nhiệm của mã nguồn tăng lên đôi chút, nhưng các đội ngũ phụ thuộc nhiều vào xử lý nền nặng sẽ có được một môi trường production đáng tin cậy hơn.