DevOps & giám sát

Năm lầm tưởng phổ biến về lập lịch khiến cron job chạy trùng lặp

Một phân tích kỹ thuật chỉ ra lý do tại sao các tệp khóa (lock files) và dấu thời gian không thể ngăn chặn việc thực thi trùng lặp trong các hệ thống phân tán, đồng thời đề xuất sổ cái tính bất biến (idempotency ledger) như một giải pháp.

Giá đỡ máy chủ dưới mái vòm kính với đèn cảnh báo chỉ thị trạng thái giám sát
Ảnh minh họa tạo riêng cho bài viết

Bài được dịch tự động từ bản gốc tiếng Anh.

Các nhà phát triển phụ thuộc vào bộ lập lịch cron tiêu chuẩn thường giả định rằng các công việc hàng đêm của họ sẽ được thực hiện chính xác một lần. Tuy nhiên, các phân tích kỹ thuật gần đây cho thấy giả định này thường dẫn đến việc xử lý trùng lặp và làm hỏng dữ liệu. Bài viết nhấn mạnh cách các biện pháp bảo vệ phổ biến như tệp khóa và dấu thời gian cục bộ thất bại trong môi trường container hóa hiện đại, gây ra nhiều rắc rối vận hành đáng kể cho các đội ngũ quản lý cơ sở hạ tầng tự lưu trữ (self-hosted).

Chuyện gì đã xảy ra

Tác giả Jordan Huang phát hiện ra rằng một công việc phân loại (triage) hàng đêm đã được thực hiện ba lần cho cùng một đầu vào, dẫn đến các bình luận trùng lặp trên cùng một vấn đề (issue). Sự cố này đã thúc đẩy việc xem xét lại năm niềm tin lâu đời về lập lịch công việc, mỗi niềm tin đều bị chứng minh là sai trong bối cảnh phân tán hoặc container hóa. Vấn đề cốt lõi không phải là lỗi logic ứng dụng, mà là sự hiểu lầm cơ bản về cách các bộ lập lịch đảm bảo việc thực thi.

Hầu hết các bộ lập lịch cung cấp cơ chế "gửi ít nhất một lần" (at-least-once delivery) thay vì ngữ nghĩa "gửi chính xác một lần" (exactly-once semantics). Khi một công việc thất bại với mã thoát khác không, chồng lấn với lần chạy theo lịch tiếp theo, hoặc được khởi động lại thủ công, nó có thể được thực hiện nhiều lần. Nếu không có các kiểm soát tính bất biến (idempotency) rõ ràng, những lần chạy trùng lặp này sẽ tạo ra các tác dụng phụ lặp đi lặp lại, chẳng hạn như gửi nhiều email hoặc tạo các bản ghi cơ sở dữ liệu trùng nhau. Tác giả lập luận rằng việc ngăn chặn các bản sao này là trách nhiệm của nhà phát triển ứng dụng, chứ không phải của bộ lập lịch.

Để giải quyết vấn đề này, bài viết đề xuất sử dụng một sổ cái tính bất biến (idempotency ledger) được hỗ trợ bởi SQLite hoặc Redis. Sổ cái này sử dụng các ràng buộc duy nhất (unique constraints) hoặc các thao tác set nguyên tử để đảm bảo rằng một phiên bản công việc cụ thể tuyên bố quyền sở hữu nhiệm vụ trước khi thực hiện. Nếu một nỗ lực thứ hai cố gắng tuyên bố cùng một nhiệm vụ, cơ sở dữ liệu sẽ từ chối nó, ngăn chặn công việc trùng lặp. Cách tiếp cận này chuyển đổi cam kết độ tin cậy từ hệ thống tệp mong manh hoặc đồng hồ tường (wall clock) sang trạng thái bền vững của cơ sở dữ liệu.

Chi tiết quan trọng

  • Tệp khóa không đáng tin cậy giữa các máy chủ hoặc container khác nhau vì chúng không chia sẻ hệ thống tệp; các tệp còn sót lại có thể chặn các lần chạy hợp lệ mà không báo hiệu quy trình đang hoạt động.
  • Dấu thời gian cục bộ trở nên mơ hồ trong quá trình chuyển đổi giờ tiết kiệm ánh sáng ban ngày (DST) và có thể bị trôi lệch giữa các container, khiến chúng không phù hợp để sắp xếp sự kiện hoặc phát hiện trùng lặp.
  • Các mục nhật ký (log entries) không xác nhận việc thực thi thành công vì bộ đệm đầu ra có thể bị mất nếu quy trình gặp sự cố, nghĩa là một dòng log có thể tồn tại mà hành động liên quan chưa hoàn tất.
  • Việc thử lại (retries) không hề vô hại ngay cả đối với các cuộc gọi rẻ tiền, vì chúng có thể kích hoạt các tác dụng phụ bên ngoài như bình luận công khai hoặc phí API khó có thể hoàn tác.
  • Một sổ cái tính bất biến sử dụng ràng buộc duy nhất của cơ sở dữ liệu hoặc tập hợp nguyên tử Redis cung cấp một cơ chế mạnh mẽ để ngăn chặn xử lý trùng lặp bằng cách đảm bảo chỉ một worker tuyên bố một ID công việc cụ thể.
  • Sổ cái dựa trên SQLite giả định một máy chủ đơn lẻ và hệ thống tệp cục bộ, do đó chúng không phù hợp với lưu trữ gắn mạng (network-mounted storage) hoặc cụm đa nút nếu không có thêm cơ chế phối hợp.

Bối cảnh

Tính bất biến (Idempotency) là thuộc tính của các thao tác mà việc thực hiện cùng một hành động nhiều lần có tác dụng giống như thực hiện nó một lần. Trong các hệ thống phần mềm, điều này rất quan trọng để xử lý các lần thử lại và tin nhắn trùng lặp. Ví dụ, việc thanh toán thẻ tín dụng phải có tính bất biến để tránh tính phí kép, trong khi việc ghi log sự kiện có thể không cần thiết nếu các bản sao được chấp nhận. Để đạt được tính bất biến, thường cần theo dõi trạng thái của từng thao tác một cách độc đáo.

Các hệ thống phân tán giới thiệu sự phức tạp vì các thành phần có thể không chia sẻ bộ nhớ hoặc hệ thống tệp. Các công cụ Unix truyền thống như flock dựa vào khóa tệp cục bộ, vốn sẽ hỏng khi các công việc chạy trên các máy chủ khác nhau hoặc trong các container tạm thời (ephemeral containers). Tương tự, thời gian đồng hồ tường không đơn điệu và có thể nhảy lùi hoặc tiến, khiến nó trở thành nguồn thông tin không đáng tin cậy để sắp xếp sự kiện. Các phương pháp hiện đại ưu tiên máy trạng thái dựa trên cơ sở dữ liệu hoặc các thao tác nguyên tử trong các kho lưu trữ key-value như Redis để phối hợp công việc giữa các nút.

Tại sao điều này quan trọng

Đối với các đội ngũ chạy phần mềm tự lưu trữ, việc thực thi công việc trùng lặp có thể dẫn đến dữ liệu không nhất quán, tăng chi phí và gây nhầm lẫn cho người dùng. Nếu một script sao lưu chạy hai lần, nó có thể lãng phí dung lượng lưu trữ và băng thông. Nếu một dịch vụ thông báo gửi các cảnh báo trùng lặp, nó làm xói mòn niềm tin vào hệ thống. Hiểu rằng các bộ lập lịch không đảm bảo việc thực thi chính xác một lần buộc các kỹ sư phải thiết kế các ứng dụng có khả năng chống chịu tốt với việc lặp lại.

Việc dựa vào các tệp khóa đơn giản hoặc dấu thời gian tạo ra cảm giác an toàn giả tạo. Những phương pháp này hoạt động trong các cấu hình máy chủ đơn lẻ nhưng thất bại âm thầm trong các môi trường mở rộng quy mô. Bằng cách áp dụng sổ cái tính bất biến, các đội ngũ có thể đảm bảo rằng các tác vụ nền tảng quan trọng, như đồng bộ hóa dữ liệu hoặc tạo báo cáo, được thực hiện đúng cách bất kể hành vi của bộ lập lịch hay sự cố hạ tầng. Sự chuyển đổi này giảm bớt nhu cầu dọn dẹp và gỡ lỗi thủ công sau các lần chạy thất bại.

Bạn có thể làm gì

  • Kiểm tra các cron job hiện có để xác định những job có tác dụng phụ bên ngoài, chẳng hạn như gửi email hoặc ghi vào API của bên thứ ba, và đánh dấu chúng là yêu cầu kiểm soát tính bất biến.
  • Thay thế các cơ chế khóa dựa trên tệp bằng ràng buộc duy nhất của cơ sở dữ liệu hoặc lệnh nguyên tử Redis để quản lý việc tuyên bố công việc giữa nhiều worker hoặc container.
  • Lưu trữ tất cả các dấu thời gian ở định dạng UTC ISO-8601 để tránh sự mơ hồ trong quá trình chuyển đổi giờ tiết kiệm ánh sáng ban ngày và đảm bảo thứ tự nhất quán giữa các hệ thống khác nhau.
  • Triển khai cơ chế heartbeat hoặc cập nhật trạng thái, nơi công việc chỉ cập nhật bản ghi trạng thái bền vững sau khi tác dụng phụ được xác nhận, thay vì dựa vào đầu ra của log.
  • Cấu hình bộ lập lịch để cho phép các lần chạy chồng lấn chỉ khi logic công việc kiểm tra rõ ràng sự tồn tại của công việc thông qua sổ cái tính bất biến, ngăn ngừa các điều kiện tranh chấp (race conditions).
  • Kiểm tra khả năng phục hồi của công việc bằng cách kích hoạt thủ công các lần chạy trùng lặp trong môi trường staging để xác minh rằng các kiểm tra tính bất biến từ chối đúng cách nỗ lực thứ hai.

Tin khác

DevOps & giám sát

Ngừng thử lại các bài kiểm tra email trong CI Kubernetes và bắt đầu thuê hộp thư

Việc coi hộp thư kiểm thử như các phụ thuộc HTTP có thể thử lại được sẽ gây ra các pipeline CI không ổn định. Việc thuê các hộp thư riêng biệt với thời hạn hết hiệu lực rõ ràng giúp ngăn chặn tin nhắn trùng lặp và điều kiện tranh chấp (race conditions).

Tất cả tin