Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 4 — Undo, idempotency và thực thi an toàn

Đã đọc 0/7 mục · còn khoảng 4 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Approval gate ngăn hành động sai trước khi xảy ra. Nhưng người duyệt cũng sai, và một số hành động vẫn chạy tự động. Nên tầng phòng thủ thứ hai là khả năng hoàn tác (undo) và đảm bảo hành động không bị thực thi hai lần (idempotency).

Ba loại hành động theo khả năng undo

  • Undo được hoàn toàn: cập nhật DB có bản ghi cũ → phục hồi được. Đổi trạng thái đơn hàng.
  • Bù trừ được (compensating action): không xóa được hành động gốc, nhưng có hành động ngược để bù. Đã hoàn tiền → tạo lệnh thu lại; đã gửi email → gửi email đính chính.
  • Không thể undo: gửi tin nhắn tới hàng loạt người, xóa vĩnh viễn không backup, thao tác vật lý. Với loại này, phòng ngừa (gate) là tất cả những gì bạn có.
Nguyên tắc thiết kế: cố gắng biến hành động không undo được thành undo được. Ví dụ, thay vì xóa cứng, dùng soft-delete (đánh dấu deleted_at) rồi mới dọn thật sau 30 ngày.

Cửa sổ undo (undo window)

Với hành động mức L2, thay vì chặn chờ duyệt, ta cho chạy ngay nhưng trì hoãn hiệu lực thật:

def send_with_undo_window(email, delay_minutes=5):
    job = schedule_at(now() + timedelta(minutes=delay_minutes),
                      lambda: smtp.send(email))
    notify_operator(f"Email sẽ gửi sau {delay_minutes} phút",
                    undo_link=f"/undo/{job.id}")
    return job.id

def undo(job_id):
    cancel_scheduled(job_id)   # nếu chưa tới giờ, hủy sạch

Đây chính là cơ chế "Undo Send" của Gmail. Nó cho tốc độ (không chờ duyệt) mà vẫn có phanh.

Idempotency: chống thực thi trùng

Khi có retry, resume sau duyệt, hoặc network glitch, cùng một hành động có thể chạy hai lần → hoàn tiền đôi, gửi mail đôi. Giải pháp là idempotency key: một khóa duy nhất cho mỗi ý định hành động.

def execute_once(action, idem_key):
    if store.exists(idem_key):
        return store.get_result(idem_key)   # đã chạy, trả kết quả cũ
    result = action.run()
    store.save(idem_key, result)            # ghi trước khi trả về
    return result

# idem_key nên bắt nguồn từ Ý ĐỊNH, không phải thời điểm:
idem_key = f"refund:order-{order_id}:v{revision}"

Điểm quan trọng: key phải phản ánh hành động cụ thể muốn thực hiện, để hai lần retry cùng ý định có cùng key, còn hai ý định khác nhau thì key khác.

Các bước làm cho thực thi an toàn

  • Phân loại mỗi hành động theo ba nhóm undo ở trên; ghi vào policy.
  • Với nhóm không undo được, tìm cách biến đổi: soft-delete, gửi thử, trì hoãn hiệu lực.
  • Gắn cửa sổ undo cho hành động L2; nối nút Undo vào thông báo cho người vận hành.
  • Sinh idempotency key từ ý định cho mọi hành động write; lưu key + kết quả.
  • Ghi lại hành động bù trừ cho những gì đã lỡ chạy nhưng cần hủy.
  • Kiểm thử đường resume: cố tình gọi lại hành động đã duyệt để chắc chắn không nhân đôi.

Khung tư duy: thang phòng thủ

Phòng ngừa (gate)  →  Trì hoãn (undo window)  →  Bù trừ (compensate)
     mạnh nhất              trung bình                 cuối cùng

Luôn ưu tiên tầng bên trái; càng về phải càng tốn kém và không hoàn hảo.

Sai lầm thường gặp

  • Lấy timestamp làm idempotency key. Hai lần retry sẽ có timestamp khác nhau → coi là hai hành động → thực thi đôi. Key phải bắt nguồn từ ý định.
  • Ghi kết quả sau khi trả về. Nếu lưu key sau khi hành động chạy nhưng trước lúc lưu bị crash, lần sau chạy lại. Lưu trong cùng giao dịch với hành động khi có thể.
  • Coi mọi thứ đều undo được. Gọi API bên thứ ba (chuyển khoản, gửi SMS) thường không undo — đừng thiết kế như thể bấm Ctrl+Z là xong.
  • Cửa sổ undo quá ngắn hoặc quá dài. Quá ngắn thì không kịp phát hiện; quá dài thì khách hàng chờ và mất tính realtime.
  • Quên hành động bù trừ cũng có rủi ro. Lệnh "thu lại tiền" cũng là một write cần đi qua gate/audit như hành động gốc.
Bài sau: xây audit trail để mọi hành động — tự động hay được duyệt — đều truy vết được, phục vụ điều tra và tuân thủ.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi