Batch Python crash 3 giờ sáng

Nay dọn dẹp tài liệu nhớ ra đã note (nhật ký thì đúng hơn) lại một issue hay ho chia sẻ cho mọi người luôn cho thực tế (kẻo quên ấy mà) và cũng vì những năm tháng đó tuy đã có title DevOps Engineer nhưng mà vẫn còn dính cái tư duy coder python lắm (timeline cụ thể ở cuối nhé).

Trong sự nghiệp của tôi, sau một quãng thời gian lăn lộn với code, production incident và những đêm thức trắng cùng đội ngũ, tôi nhận ra rằng kỹ sư giỏi không được định nghĩa bởi số lượng ngôn ngữ biết, mà bởi cách chúng ta đối mặt với hỗn loạn khi hệ thống thực sự sập.

Năm thứ 4 đi làm, tôi chịu trách nhiệm cho một service Python chạy batch xử lý dữ liệu hàng đêm. Tất cả đều ổn cho tới khi một hôm, batch dừng im lặng lúc 3 giờ sáng. Không exception rõ ràng, không log ra lỗi. Team ping tôi giữa đêm. Cảm giác lúc đó không phải hoảng loạn, mà là “đây chính là lúc để chứng minh mình là kỹ sư thực sự”.

Tôi không ngồi debug Python script như khi còn 1-2 năm exp nữa. Tôi chia nhỏ vấn đề:

  • Tracing ở cấp OS: Dùng strace bám vào process, thấy các syscall treo ở I/O. Không phải bug Python, mà là một external dependency.
  • Tài nguyên hệ thống: Check dmesgtop, phát hiện I/O wait tăng vọt, disk gần như choke.
  • Nguyên nhân gốc rễ: Hóa ra có một cron job backup chạy trùng thời điểm, chiếm hết I/O, khiến batch đọc dữ liệu bị block.

Điều thú vị là: nếu chỉ tư duy ở mức code, tôi sẽ không bao giờ giải quyết nổi. Bài học sau đó, tôi setup cụ thể hơn hạ tầng monitoring hiện có và thêm tracing với OpenTelemetry, và quan trọng hơn là đặt lại lịch cho các batch và backup job để tránh đụng nhau.

Với kinh nghiệm, tôi cũng thay đổi cách viết code. Ví dụ, khi xử lý dữ liệu với Python, thay vì assume input luôn sạch sẽ như trong sách vở, tôi luôn coi input có thể “bẩn”: thiếu key, sai type, hoặc null bất ngờ. Tôi viết wrapper validator, bổ sung metric invalid_input_count để vừa bảo vệ hệ thống, vừa giúp team data upstream có feedback ngay.

Có lần phỏng vấn một bạn trẻ, bạn ấy trả lời rất tốt câu hỏi về list vs tuple, về decorator, về Big-O. Nhưng khi tôi hỏi: “Giả sử service của bạn cứ crash một lần/ngày, log không ghi gì cả, bạn làm thế nào?” bạn ấy chỉ nói “em sẽ thêm nhiều log hơn”. Tôi hiểu ngay đó là khoảng cách giữa lý thuyết và thực chiến.

Sau đợt đấy thì cũng rút ra: kỹ sư không được trả tiền chỉ để viết code đúng, mà để giữ cho hệ thống sống sót trong môi trường đầy bất định. Bạn có thể biết yield, async, hay OOP nâng cao, nhưng giá trị thật nằm ở khả năng:

  • Phân tích hệ thống như một chỉnh thể (từ app đến OS, network, infra).
  • Xử lý dữ liệu bẩn, ngoại lệ không đoán trước.
  • Xây dựng quy trình để vấn đề chỉ xảy ra một lần duy nhất rồi biến thành tri thức của team.

Với tôi, đó mới là dấu mốc biến một developer thành một engineer thực sự. Vì lúc đó bản chất vẫn là một “Dev ôm vận hành” chứ chưa được như bây giờ 🙂

Postmortem: Batch Python crash 3 giờ sáng

Dưới đây là thông tin cụ thể mà tôi note lại trong docs cho cái khoảnh khắc đáng nhớ để có bài chia sẻ này nhé.

  • Thời điểm xảy ra: 03:12 sáng, ngày 23 tháng 08 năm bí mật :v
  • Impact: Batch xử lý dữ liệu hàng ngày không hoàn thành, ước tính ~15% báo cáo ngày hôm sau bị thiếu dữ liệu.
  • Detection: Alert từ Prometheus: job_duration_seconds > threshold. PagerDuty gọi on-call.

Timeline

  • 03:12: Alert nổ, batch dừng mà không có exception trong log.
  • 03:20: SSH vào server, thấy process Python vẫn tồn tại nhưng không tiến triển. CPU 0%, I/O wait tăng bất thường.
  • 03:25: Dùng `strace -p `, thấy process block ở system call `read()`.
  • 03:40: Kiểm tra iostat, disk I/O 100% util, queue depth cao bất thường.
  • 03:50: Phát hiện cron job backup chạy song song vào đúng khung 3h sáng, ghi file lớn vào cùng mount point.
  • 04:10: Kill job backup, batch tiếp tục chạy được.

Root Cause

Batch Python không crash do bug ứng dụng, mà bị resource starvation (disk I/O choke). Cron job backup chiếm toàn bộ băng thông I/O, khiến batch bị treo ở syscall read(). Vì code không có timeout/fail-fast logic, process treo vô thời hạn.

Fix tạm thời

  • Thay đổi lịch cron backup sang 1h sáng.
  • Restart batch để hoàn thành trong đêm đó.

Action Items

  1. Application-level defensive coding:

    • Thêm timeout cho các thao tác I/O. Nếu batch không đọc được file trong X giây => fail-fast, raise exception thay vì treo vô hạn.
    • Log thêm progress checkpoint để phân biệt giữa “batch chậm” và “batch treo”.
  2. System-level mitigation:

    • Thêm monitoring cho disk I/O (Prometheus node exporter: node_disk_io_time_seconds_total).
    • Dashboard Grafana hiển thị I/O wait và job concurrency.
  3. Process coordination:

    • Thiết lập job orchestrator (Airflow/Luigi) thay vì cron rời rạc. Orchestrator đảm bảo job không chạy đụng nhau.
  4. Knowledge sharing:

    • Ghi lại case này như một “known failure mode”. Toàn team phải biết rằng lỗi production không chỉ đến từ code, mà có thể do hạ tầng hoặc các job chạy song song.

Lessons Learned

  • Nếu ít kinh nghiệm thường chỉ nhìn vào stacktrace. Với người có kinh nghiệm, phải coi process như một “blackbox” trong hệ thống, rồi debug từ ngoài vào: syscall, resource, OS.
  • Cron job, batch, backup mọi thứ đều là một phần của hệ thống. Nếu không quản lý đồng bộ, code dù hoàn hảo cũng sẽ fail.
  • Code robust + infra discipline mới ngăn được sự cố tái diễn.
Chia sẻ bài viết:
Theo dõi
Thông báo của
0 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Phản hồi nội tuyến
Xem tất cả bình luận