DHDataHọc
Lý thuyếtBài 5/84 phút đọc+10 XP

Xử lý lỗi và retry

Bạn sẽ học được

  • Cấu hình retry hợp lý cho từng loại lỗi.
  • Phân biệt lỗi tạm thời và lỗi vĩnh viễn.
  • Dùng timeout để tránh task treo.
  • Gắn callback cảnh báo khi thất bại.
  • Hiểu vì sao retry có thể làm hỏng dữ liệu.

Khái niệm cốt lõi

Task có thể thất bại vì nhiều lý do:

LoạiVí dụRetry có ích?
Tạm thờiMạng chập, API quá tảiCó
Tài nguyênHết bộ nhớ, hết dung lượngCó, nếu đã xử lý nguyên nhân
Dữ liệuNguồn rỗng, schema đổiKhông
LogicBug trong codeKhông
QuyềnSai thông tin đăng nhậpKhông

Retry mù quáng biến lỗi nhanh thành lỗi chậm, và với task không idempotent, biến lỗi thành sai dữ liệu.

Cấu hình cơ bản

code
from datetime import timedelta

default_args = {
    "retries": 3,
    "retry_delay": timedelta(minutes=5),
    "retry_exponential_backoff": True,
    "max_retry_delay": timedelta(minutes=60),
    "execution_timeout": timedelta(hours=2),
}
  • retries=3: thử tối đa ba lần sau lần đầu.
  • retry_delay: chờ 5 phút giữa các lần.
  • retry_exponential_backoff: 5, 10, 20 phút — tránh dồn dập vào API đang quá tải.
  • execution_timeout: dừng task treo, tránh chiếm slot vô hạn.

Timeout quan trọng hơn retry

Một task treo không bao giờ kết thúc sẽ chiếm worker mãi mãi. Không có timeout, retry không bao giờ được kích hoạt vì task chưa thất bại.

Đặt timeout theo thời gian chạy bình thường:

code
Chạy bình thường: 12 phút
Timeout hợp lý:   60 phút

Đừng đặt timeout sát thời gian bình thường — một ngày dữ liệu lớn hơn sẽ bị cắt oan.

Trạng thái task

Trạng tháiNghĩa
successThành công
failedThất bại sau khi hết retry
upstream_failedTask trước thất bại, task này không chạy
skippedBị bỏ qua theo điều kiện
up_for_retryĐang chờ thử lại

Khi debug, xem trạng thái từng task, không chỉ trạng thái DAG run. Một DAG run failed thường chỉ có một task gốc hỏng.

Callback

code
def on_failure(context):
    dag_id = context["dag"].dag_id
    task_id = context["task_instance"].task_id
    logical_date = context["logical_date"]
    print(f"LỖI {dag_id}.{task_id} tại {logical_date}")

@dag(
    dag_id="daily_orders",
    default_args={"on_failure_callback": on_failure, "retries": 2},
    ...
)

Callback chạy sau khi hết retry. Đừng gửi cảnh báo ở mỗi lần retry — sẽ tạo nhiễu.

Với thông báo quan trọng, callback nên idempotent: kiểm tra đã gửi chưa trước khi gửi lại.

SLA

code
@task(sla=timedelta(hours=4))
def load():
    ...

SLA cảnh báo khi task chạy lâu hơn dự kiến, kể cả khi cuối cùng vẫn thành công. Hữu ích cho pipeline có cam kết thời gian với người dùng cuối.

SLA không dừng task — nó chỉ gửi cảnh báo.

Retry và idempotency

Đây là điểm nguy hiểm nhất.

Task INSERT 12.000 dòng, lần đầu ghi xong nhưng lỗi khi cập nhật metadata. Retry chạy lại và ghi thêm 12.000 dòng. Kết quả: 24.000 dòng, không có lỗi nào.

Quy tắc: chỉ bật retry tự động cho task đã idempotent, hoặc cho task chỉ đọc và tính toán.

Với task ghi dữ liệu, sửa sang xoá partition hoặc MERGE trước khi bật retry.

Cảnh báo khi không có task nào chạy

Một dạng lỗi khó thấy: DAG không được lập lịch, hoặc scheduler dừng. Không có task thất bại, nên không có cảnh báo.

Cần thêm giám sát độ tươi dữ liệu: nếu bảng không có dữ liệu mới trong 26 giờ, cảnh báo. Bài 7 nói kỹ hơn.

Lỗi thường gặp

  • Retry cho task không idempotent: dữ liệu trùng.
  • Không đặt timeout: task treo chiếm worker.
  • Cảnh báo mỗi lần retry: nhiễu, mất phản ứng.
  • Retry lỗi dữ liệu: thử lại cũng thất bại, chỉ tốn thời gian.
  • Không xem trạng thái task: debug sai chỗ.
  • Không giám sát DAG im lặng: không biết pipeline đã dừng.

Thực hành

  1. Đặt retries=3 với exponential backoff.
  2. Thêm execution_timeout cho một task.
  3. Viết callback in ra DAG, task và logical date.
  4. Giải thích vì sao retry có thể gây trùng dữ liệu.
  5. Thiết kế cảnh báo cho trường hợp DAG không chạy.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
Dữ liệu trùng sau lỗiRetry task không idempotentSửa cách ghi
Task treo mãiKhông có timeoutexecution_timeout
Quá nhiều cảnh báoCallback ở mỗi retryChỉ cảnh báo sau cùng
Retry vô íchLỗi logic hoặc dữ liệuSửa gốc, giảm retry
Không biết pipeline dừngKhông giám sát độ tươiThêm kiểm tra freshness

Tóm tắt

  • Chỉ retry lỗi tạm thời.
  • Timeout bảo vệ worker khỏi task treo.
  • Cảnh báo sau khi hết retry, không phải mỗi lần.
  • Retry chỉ an toàn với task idempotent.
  • Giám sát cả trường hợp không có task nào chạy.

Dùng ← → để chuyển bài