</>Học Dev
Bài học

Tuần 6 - Ngày 5: Xử lý lỗi, Retry và Idempotency

Tuần 6 – Ngày 5

Mục tiêu học tập

  • Hệ thống hóa cơ chế retry/DLQ theo từng service trong pipeline
  • Hiểu delivery semantics: at-least-once là mặc định → phải idempotent
  • Nắm các kỹ thuật idempotent write theo từng data store
  • Thiết kế pipeline "recover được": checkpoint, backfill, reprocess

1. Delivery semantics — sự thật nền tảng

SemanticsNghĩaThực tế AWS
At-most-onceCó thể mất, không trùngHiếm dùng cho data
At-least-onceKhông mất, có thể trùngMặc định của S3 events, SQS standard, Kinesis, Lambda retry, DMS...
Exactly-onceKhông mất, không trùngChỉ trong phạm vi hỗ trợ: Flink checkpoint, Kafka transactions, FIFO dedup — end-to-end phải tự thiết kế bằng idempotency

→ Câu thần chú: "assume duplicates, design idempotent".

2. Retry và DLQ theo service (bảng tổng hợp)

ServiceCơ chế lỗi
Lambda async (S3/SNS event)Retry 2 lần → on-failure destination / DLQ
Lambda + SQSMessage quay lại queue (visibility timeout) → maxReceiveCountSQS DLQ; partial batch response
Lambda + KinesisRetry chặn shard → cấu hình max retry, bisect batch, on-failure destination
Step FunctionsRetry (backoff, maxAttempts) + Catch per state — trung tâm điều phối lỗi
Glue jobMaxRetries ở job; bookmark chỉ commit khi thành công
FirehoseRetry đích → S3 error/backup prefix — không mất data
EventBridgeRetry 24h → DLQ (SQS) per target
SQSRedrive policy → DLQ; redrive ngược DLQ → source sau khi sửa

Nguyên tắc chung: mọi điểm async đều phải có "chỗ rơi" (DLQ/error prefix) + alarm trên chỗ rơi đó.

3. Idempotency theo data store

StoreKỹ thuật ghi idempotent
S3Ghi deterministic key (cùng input → cùng path, PUT ghi đè); ghi theo partition overwrite thay vì append
IcebergMERGE INTO theo khóa — chạy lại không tạo trùng
RedshiftStaging table + DELETE ... USING staging rồi INSERT (upsert pattern), hoặc MERGE; không append mù
DynamoDBConditional write (attribute_not_exists), idempotency key table
OpenSearchIndex với document _id xác định — ghi lại là update
ChungIdempotency key từ event id; bảng marker "đã xử lý" (DynamoDB + TTL)

4. Checkpoint / restart / backfill

  • Checkpoint có sẵn: Glue bookmark, KCL lease (DynamoDB), Flink checkpoint, Spark streaming checkpoint, DMS task position — hiểu mỗi cái commit lúc nào (thường: sau khi thành công → phần lỡ dở sẽ chạy lại → cần idempotent sink)
  • Backfill/reprocess: thiết kế job nhận tham số dải thời gian/partition để chạy lại một khoảng (kết hợp bookmark pause); raw zone immutable là điều kiện để reprocess an toàn
  • Poison record: một record hỏng không được phép giết pipeline — bisect/DLQ/quarantine rồi xử lý riêng

5. Checklist thiết kế pipeline chịu lỗi

  • Mọi consumer async có DLQ/on-failure destination + alarm trên độ sâu DLQ
  • Sink idempotent (theo bảng mục 3)
  • Retry có backoff + jitter, có giới hạn (không retry vô hạn)
  • Checkpoint đúng chỗ; hiểu semantics khi fail giữa chừng
  • Job tham số hóa để backfill theo dải partition
  • Timeout mọi job/step (Step Functions TimeoutSeconds, Glue timeout)
  • Phân loại lỗi: transient (retry) vs permanent (DLQ ngay, đừng retry 24h)

Câu hỏi ôn tập

  1. Glue job nightly fail lúc 2h sáng sau khi ghi một nửa output ra S3 (append). Chạy lại sinh dữ liệu trùng. Sửa thiết kế thế nào?

    Xem đáp án

    Làm sink idempotent: (1) ghi theo partition overwrite — job ghi đè toàn bộ partition ngày đó thay vì append (chạy lại = ghi đè sạch); hoặc (2) chuyển bảng Iceberg + MERGE INTO theo khóa; hoặc (3) ghi ra staging prefix, thành công mới move/commit. Kèm bookmark hiểu rằng phần fail sẽ được đọc lại (at-least-once).

  2. Pipeline SQS → Lambda thỉnh thoảng có message lỗi vĩnh viễn (dữ liệu hỏng). Yêu cầu: không mất message, không nghẽn queue, có cảnh báo. Thiết kế?

    Xem đáp án

    Redrive policy với maxReceiveCount (vd 3) → message hỏng sang DLQ; CloudWatch alarm trên ApproximateNumberOfMessagesVisible của DLQ → SNS; sau khi sửa code/dữ liệu dùng DLQ redrive đẩy ngược về queue nguồn. Kết hợp partial batch response để không retry oan message tốt cùng batch.

  3. Nạp incremental vào Redshift mỗi giờ, đôi khi file nguồn được gửi lại (duplicate). Pattern nạp chống trùng?

    Xem đáp án

    Staging + upsert: COPY vào staging table, rồi trong một transaction DELETE FROM target USING staging WHERE khóa trùng + INSERT INTO target SELECT ... FROM staging (hoặc dùng MERGE). Chạy lại bao nhiêu lần kết quả vẫn đúng — idempotent. Append thẳng COPY vào target là nguồn gốc trùng lặp.

  4. Phân biệt lỗi transient và permanent — vì sao chiến lược retry phải khác nhau?

    Xem đáp án

    Transient (throttle, timeout mạng, service tạm lỗi): retry với exponential backoff + jitter thường tự khỏi. Permanent (dữ liệu hỏng, schema sai, AccessDenied): retry chỉ đốt tài nguyên và trì hoãn — phải fail nhanh sang DLQ/quarantine và alert. Trong Step Functions: khai báo Retry cho ErrorEquals transient, Catch cho lỗi còn lại sang nhánh xử lý.

  5. Cần chạy lại pipeline cho riêng ngày 2026-07-01 do phát hiện bug transform. Điều kiện thiết kế nào giúp việc này an toàn?

    Xem đáp án

    (1) Raw zone immutable còn nguyên dữ liệu gốc ngày đó; (2) job tham số hóa theo partition (--date 2026-07-01) thay vì "xử lý những gì mới"; (3) sink idempotent theo partition (overwrite/MERGE) để ghi lại không trùng; (4) bookmark ở chế độ pause hoặc đường chạy backfill riêng không phá state incremental. Thiếu các điều kiện này, backfill thành nguồn lỗi mới.

Bài tập thực hành

  • Thêm DLQ + alarm cho một Lambda event source trong lab
  • Viết pattern staging-upsert (SQL) cho bảng Redshift orders
  • Sửa Glue job lab: nhận tham số --process_date và ghi overwrite partition tương ứng
  • Đọc Exponential backoff and jitter (AWS builders' library)

Tài liệu tham khảo chính thức


Tiếp theo: Quiz Tuần 6