---
title: "Xử lý timeout, retry và crash - idempotency, outbox, lease và reconciliation"
description: "Khóa học cung cấp các cơ chế bảo vệ dữ liệu khi gọi service bên ngoài gặp timeout, crash hay retry. Bạn sẽ làm chủ idempotency key, outbox, lease và reconciliation để retry không trừ tiền hai lần, worker chết không sót email và workflow giữ nguyên hạn chờ sau deploy, thay thế phỏng đoán bằng bằng chứng xác thực."
canonical: "https://200lab.io/courses/xu-ly-timeout-retry-va-crash-idempotency-outbox-lease-va-reconciliation"
type: "course"
course_type: "course"
learning_type: "free_form"
published: "2026-10-02T17:15:12Z"
students: 102
---

## Khóa học này mang lại gì cho bạn

Khi hệ thống của bạn mở rộng và giao tiếp qua network, các thao tác có side effect như trừ tiền, gửi email hay tạo vận đơn luôn phải đối mặt với timeout, retry và crash. Những sự cố này thường khiến lệnh bị chạy lặp lại, bị bỏ quên hoặc rơi vào vùng mù thông tin. Khóa học cung cấp những nền tảng kỹ thuật vững chắc để bạn cô lập rủi ro, retry an toàn và phục hồi tính toàn vẹn của dữ liệu sau sự cố.

- **Xác định kết quả chưa rõ** — Bạn dựng ba timeline độc lập của một request có side effect để thấy chính xác lúc server của bạn đã ngừng chờ nhưng service bên ngoài vẫn đang xử lý dở dang.
- **Phân loại lỗi trước khi retry** — Bạn phân biệt rành mạch giữa thao tác chỉ đọc và thay đổi dữ liệu, đồng thời kết hợp backoff, jitter và retry budget để vòng retry không làm sự cố quá tải kéo dài thêm.
- **Chặn thao tác chạy trùng** — Bạn gắn idempotency key với ý định nghiệp vụ của client và dùng precondition kèm compare-and-set để chặn những lệnh được duyệt trên dữ liệu cũ nhưng tới lúc chạy thì trạng thái đã đổi.
- **Bảo vệ dữ liệu ngoài transaction** — Bạn áp dụng transactional outbox khi ghi database và gọi service bên ngoài không thể nằm chung một transaction, kết hợp bảng inbox ở consumer để xử lý an toàn message bị giao lại.
- **Chuyển giao quyền chạy job** — Bạn kết hợp lease với fencing token để worker cũ bị treo không ghi đè dữ liệu của worker mới, đồng thời checkpoint từng bước để job nhiều bước chạy tiếp đúng chỗ sau crash.
- **Phục hồi dựa trên bằng chứng** — Bạn đọc lại dữ liệu từ source of truth hoặc gửi lại với idempotency key cũ khi có cam kết chống trùng, từ đó quyết định chính xác giữa retry, reconcile hay compensate.
- **Đo lường bằng số liệu** — Bạn viết câu truy vấn đếm số bản ghi trùng, số lệnh kẹt pending và mức lệch số dư tài khoản trên database thực tế để kiểm chứng hệ thống trước và sau khi triển khai giải pháp.

Đây là khóa học thiết kế cơ chế vận hành phân tán ở tầng app và database, giữ tính độc lập với framework hay hạ tầng cụ thể.

## Khóa học cung cấp những nền tảng nào

Nội dung khóa học được xây dựng xoay quanh khái niệm **kết quả chưa rõ** (ambiguous outcome), tức thời điểm service của bạn đã gửi request đi nhưng không có đủ bằng chứng để khẳng định phía đối tác đã thực hiện thao tác hay chưa. Nền tảng phân tích này kế thừa bài viết [Making retries safe with idempotent APIs](https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/) của AWS về giới hạn của timeout, kiến trúc [Transactional outbox](https://microservices.io/patterns/data/transactional-outbox.html) của Chris Richardson nhằm đồng bộ dữ liệu giữa database với network, cùng nghiên cứu [How to do distributed locking](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html) của Martin Kleppmann về lease và fencing token. Các bài học đi qua sáu nhóm cơ chế cốt lõi và một chương bài tình huống thực chiến để bạn áp dụng trực tiếp vào hệ thống production.

### 1. Đọc timeline của lời gọi bị timeout, rồi đặt deadline và retry cho đúng

Nhóm bài đầu tiên đặt một request gọi sang service bên ngoài lên ba trục thời gian tách biệt: request của client, trạng thái bản ghi trong database và side effect thực tế tại đối tác. Bạn sẽ thấy rõ nguyên nhân vì sao gateway báo lỗi 504 sau **10 giây** nhưng handler phía sau vẫn âm thầm chạy tiếp tới giây thứ **36**, từ đó thiết lập deadline propagation và AbortSignal để huỷ câu query kịp thời. Bạn cũng phân tích hiện tượng hai request chạy song song để nhận diện lost update và check-then-act. Trước khi cho phép retry, bạn học cách lập bảng phân loại lỗi theo bản chất transient, permanent hoặc ambiguous, đồng thời áp dụng exponential backoff, jitter và token bucket retry budget để ngăn ngừa retry storm khi upstream gặp sự cố.

### 2. Chống chạy trùng bằng idempotency key và precondition

Mỗi request ID sinh mới ở tầng middleware không thể ngăn chặn việc trừ tiền lần hai khi client gửi lại request sau một lần rớt mạng. Bạn sẽ thiết kế bảng lưu idempotency key gắn liền với ý định nghiệp vụ, lưu lại kết quả committed response và tính fingerprint để từ chối các request tái sử dụng key nhưng đổi tham số. Bên cạnh đó, bạn xử lý triệt để bài toán stale approved intent, nơi một lệnh hoàn tiền được duyệt hợp lệ lúc **10:00** nhưng đến **10:05** mới thực thi khi dữ liệu gốc đã bị luồng khác thay đổi. Cơ chế precondition bằng compare-and-set kết hợp cột version trong database giúp câu lệnh ghi tự kiểm tra điều kiện ngay tại thời điểm ghi để từ chối các thao tác không còn hợp lệ.

### 3. Lời gọi nằm ngoài transaction: outbox và consumer chịu được message bị giao lại

Khi một tiến trình bị OOMKilled trong khoảng trống vài chục mili-giây giữa lúc commit đơn hàng và lúc gọi API gửi email, dữ liệu nội bộ đã lưu nhưng thông báo ra bên ngoài bị mất vĩnh viễn. Khóa học trang bị mô hình transactional outbox để đưa ý định gọi service ngoài vào cùng database transaction với dữ liệu nghiệp vụ, giao cho relay worker quét gửi với cam kết at-least-once. Ở đầu nhận, message queue có thể giao lại tin nhắn sau visibility timeout khi consumer crash trước lúc gửi ack. Bạn sẽ dựng bảng inbox ghi nhận event id trong cùng transaction với thay đổi nghiệp vụ để consumer tự bỏ qua các message trùng lặp mà không làm sai lệch số liệu.

### 4. Job vẫn chạy đúng khi worker chết hoặc bị treo: lease, fencing token và checkpoint

Cơ chế phân chia công việc bằng lease không thể bảo vệ dữ liệu nếu worker cũ gặp GC pause kéo dài hoặc bị treo mạng rồi bất ngờ hoạt động trở lại sau khi lease đã hết hạn. Bạn sẽ bổ sung fencing token tăng dần ở mỗi lần nhận lease, buộc database đích kiểm tra token trong cùng transaction ghi để chặn đứng các đợt ghi đè dữ liệu từ worker đã mất quyền. Đối với các background job nhiều bước có gọi service tốn phí, mô hình checkpoint lưu trữ output của từng bước vào bảng trạng thái. Khi worker mới nhận lại job sau crash, cơ chế replay chỉ chạy lại code điều phối và đọc lại kết quả của các bước đã lưu checkpoint thay vì gọi lại service bên ngoài.

### 5. Phục hồi sau crash bằng reconciliation và compensation

Sau khi tiến trình khởi động lại, các bản ghi dở dang ở trạng thái pending không phải là căn cứ để khẳng định thao tác chưa diễn ra. Quy trình reconciliation hướng dẫn bạn tra cứu ngược lại source of truth của đối tác hoặc retry bằng idempotency key cũ để thu thập bằng chứng xác thực trước khi cập nhật dữ liệu. Khi một chuỗi nghiệp vụ phân tán gặp lỗi vĩnh viễn ở bước sau, bạn áp dụng compensation để hoàn tác các bước đã xong trước đó theo đúng trật tự nghiệp vụ. Bạn cũng phân biệt rõ giữa direct undo, compensating action và chuyển giao xử lý thủ công đối với các side effect không thể đảo ngược như email đã gửi.

### 6. Workflow chạy lâu, từ cron tới durable execution

Các tiến trình crontab chạy cục bộ trên một máy sẽ bỏ lỡ hoàn toàn các lượt thực thi khi server gặp sự cố mà không để lại bất kỳ dòng log cảnh báo nào. Bạn sẽ chuyển đổi lịch trình thành các bản ghi trong bảng `job_runs` với partial unique index để scheduler trên nhiều instance không chạy chồng chéo và hỗ trợ chính sách chạy bù linh hoạt. Đối với các mốc thời gian kéo dài nhiều ngày, bạn tổ chức bảng timer bền vững với cột `wake_at` được rải thời gian hợp lý kèm cơ chế khóa hàng loạt bằng `SKIP LOCKED`. Khóa học cũng trang bị giải pháp ghim `definition_version` cho từng run để bảo vệ các workflow đang chờ duyệt không bị chạy sai khi hệ thống deploy phiên bản code mới.

### 7. Sáu bài tình huống nghiệp vụ

Các bài tình huống đưa toàn bộ lý thuyết vào các bài toán kinh doanh cụ thể có đo lường đối chiếu trước và sau khi vá lỗi. Bạn sẽ cân nhắc tradeoff khi giữ chỗ có thời hạn bằng Redis Lua script so với exclusion constraint của Postgres khi có hàng trăm request đồng thời. Bạn xây dựng vòng lặp cho agent gọi tool hoàn tiền an toàn bằng cách quản lý step id riêng biệt, thiết kế state machine chống webhook trùng cho cổng thanh toán, và bảo vệ số dư ví điện tử bằng sổ cái double-entry ledger. Hai bài tình huống cuối giải quyết bài toán điều phối **100.000 đơn hàng** trả sau với một worker duy nhất và phương pháp tính ngày dự kiến kiểm tra để chỉ bỏ code của version cũ khi version đó không nhận run mới và không còn run chưa kết thúc nào ghim nó.

## Vì sao 200Lab tạo ra khóa học này

Trong kiến trúc phần mềm hiện đại, hầu hết các backend đều phụ thuộc chặt chẽ vào các API bên thứ ba như cổng thanh toán, đơn vị giao vận, nhà cung cấp email hoặc các mô hình AI. Những thao tác này một khi đã đi qua network thì không còn nằm trong phạm vi kiểm soát của transaction database nội bộ. Trong khi đó, các thư viện HTTP client, SDK và message broker ngày nay đều tích hợp sẵn tính năng tự động retry chỉ với một dòng cấu hình.

Cái bẫy phổ biến nhất là lập trình viên thường bật retry theo phản xạ tự nhiên mà không lường trước được rằng request trước đó có thể đã hoàn tất ở phía đối tác. Khi gặp timeout hoặc mất kết nối mạng, server của bạn chỉ biết duy nhất một điều là nó đã ngừng chờ, chứ không thể biết đối tác đã trừ tiền khách hay chưa. Nếu vội vàng retry, khách hàng sẽ bị trừ tiền hai lần; nếu vội vã đánh dấu đơn hàng thất bại trong khối catch, hệ thống sẽ bỏ rơi một giao dịch đã thành công ngoài đời thực.

200Lab nhận thấy mô thức sự cố này lặp đi lặp lại ở rất nhiều hệ thống: số dư ví bị lệch sau đợt deploy, hàng chục email gửi trùng cho một sự kiện, hoặc worker nền xuất hóa đơn gấp đôi chỉ vì một khoảng dừng garbage collection. Khi sự cố xảy ra, các đội ngũ thường mất hàng giờ đối chiếu log thủ công và phải xử lý khiếu nại tài chính phức tạp với khách hàng.

Khóa học này được 200Lab thiết kế nhằm giúp bạn làm chủ các cơ chế phân tán cốt lõi như idempotency key, outbox pattern, lease fencing và reconciliation. Bạn sẽ hiểu rõ ranh giới của từng công cụ, biết chính xác những đánh đổi về hiệu năng và dung lượng lưu trữ, từ đó đưa ra quyết định thiết kế vừa vặn để bảo vệ tính toàn vẹn của dữ liệu trên hệ thống thực tế.

## Những vấn đề thường gặp khi xử lý timeout, retry và crash

Khi một lời gọi qua network gặp trục trặc, phản xạ tự nhiên của kỹ sư thường là suy diễn trạng thái từ mã lỗi cục bộ thay vì tìm kiếm bằng chứng xác thực từ nơi nắm giữ dữ liệu thật. Những triệu chứng dưới đây phản ánh rõ nét hậu quả của thói quen lập trình đó trên môi trường production:

- Client nhận mã lỗi HTTP 504 Gateway Timeout nhưng tài khoản ngân hàng của khách vẫn bị trừ tiền — Gateway hoặc proxy hết hạn chờ upstream và trả mã lỗi 504 trong lúc cổng thanh toán vẫn đang tiếp tục xử lý lệnh trừ tiền với ngân hàng.
- Lưu lượng gửi sang nhà cung cấp email tăng vọt gấp bốn lần khi dịch vụ của họ chập chờn — Các worker thử lại ngay lập tức mà không có khoảng chờ giãn cách ngẫu nhiên và không bị chặn bởi trần retry budget, tạo nên cơn bão retry storm.
- Database đã commit đơn hàng thành công nhưng người mua không bao giờ nhận được email xác nhận — Tiến trình app bị crash ngay sau khi commit dữ liệu nội bộ và trước khi request gửi email kịp truyền đi qua network.
- Khách hàng được cộng điểm thưởng hai lần sau mỗi đợt rolling deploy của backend — Consumer xử lý xong nghiệp vụ nhưng bị tắt đột ngột trước khi kịp gửi tín hiệu ack, khiến message queue kích hoạt cơ chế redelivery giao lại tin nhắn cho worker mới.
- Bảng dữ liệu xuất hiện **820 hóa đơn** dù thực tế kho chỉ có **620 đơn hàng** thành công — Worker cũ bị tạm dừng lâu hơn thời hạn lease nên worker mới đã tiếp quản job, sau đó worker cũ hoạt động trở lại và ghi thêm 200 hóa đơn cho các đơn đã được worker mới xuất hóa đơn do database thiếu bước kiểm tra fencing token.
- Hệ thống gửi lại toàn bộ lệnh chi tiền đang treo khiến **34 người bán** nhận tiền hai lần — Job phục hồi tự phỏng đoán trạng thái từ các bản ghi pending nội bộ thay vì truy vấn đối soát dữ liệu thật từ cổng thanh toán đối tác.
- Báo giá gửi cho khách hàng doanh nghiệp mang giá trị thấp hơn mức trưởng nhóm đã duyệt — Workflow đang tạm dừng chờ duyệt được đánh thức và chạy tiếp trên phiên bản code mới vừa deploy vốn đã thay đổi cấu trúc tính chiết khấu.
- Trạng thái dữ liệu bị sai lệch sau khi xử lý sự cố — Quyết định cập nhật trạng thái dựa trên phỏng đoán chủ quan trong code thay vì bằng chứng kỹ thuật khách quan.

## Bạn sẽ học theo cách nào

- **Xuất phát từ triệu chứng kỹ thuật thực tế** — Mỗi bài học mở đầu bằng một sự cố production cụ thể với các dạng bằng chứng kỹ thuật tùy bài như dòng log, mã lỗi, số liệu metric minh họa, bản ghi trùng hoặc số tiền bị lệch.
- **Phân tích trên cùng trục thời gian** — Bạn bóc tách xung đột dữ liệu qua timeline xen kẽ của các request chạy song song, và dùng sơ đồ ba timeline đối chiếu giữa request, database state với side effect bên ngoài để phân tích các lời gọi có kết quả chưa rõ.
- **Đi từ đoạn code lỗi đến bản vá hoàn chỉnh** — Bạn quan sát trực tiếp khoảng hở race condition trên từng đoạn code TypeScript ngắn, sau đó đối chiếu với diff sửa lỗi chuẩn xác để hiểu rõ cơ chế vận hành.
- **Kiểm chứng bằng truy vấn đo lường** — Bạn được cung cấp các câu lệnh SQL kiểm tra invariant trực tiếp trên database đang chạy để xác nhận số bản ghi vi phạm đã thực sự giảm về **0**.
- **Thực hành với demo kit độc lập** — Các bài học đi kèm bộ kịch bản dựng môi trường bằng Docker giúp bạn tự tay tái hiện lỗi rớt mạng, crash tiến trình và kiểm tra kết quả sau khi vá mã nguồn.

## Khóa học dành cho ai

1. **Backend developer trực tiếp vận hành hệ thống** — Bạn cần nắm vững kỹ thuật cô lập lỗi khi gọi API bên ngoài, đảm bảo dữ liệu đơn hàng và giao dịch không bị sai lệch mỗi khi server restart hoặc gặp timeout.
2. **Kỹ sư tích hợp giải pháp thanh toán và đối tác** — Bạn phụ trách tích hợp cổng thanh toán, cổng chuyển khoản hoặc webhook đối tác, cần thiết kế luồng xử lý chặt chẽ để loại bỏ hoàn toàn các rủi ro trừ tiền hoặc chi tiền trùng lặp.
3. **Lập trình viên phát triển agent và workflow chạy lâu** — Bạn xây dựng các tác vụ tự động gọi tool có side effect hoặc các quy trình nghiệp vụ kéo dài nhiều ngày, cần cơ chế checkpoint và versioning để workflow chạy an toàn qua các đợt deploy.

## Những điều cần lưu ý

- **Không tập trung vào hướng dẫn sử dụng công cụ cụ thể** — Khóa học tập trung vào bản chất thiết kế ở tầng app và database; bạn có thể linh hoạt triển khai các cơ chế này trên Postgres, Redis, MySQL hay bất kỳ message queue nào đang dùng.
- **Không đi sâu vào các thuật toán đồng thuận hạ tầng** — Các khái niệm chuyên sâu về consensus như Raft, Paxos hay thuật toán bầu chọn leader election ở tầng mạng không thuộc phạm vi khóa học; kiến thức lease tại đây tập trung vào việc bảo vệ tính đúng đắn của dữ liệu nghiệp vụ.
- **Không bao gồm nghiệp vụ vận hành của kỹ sư SRE** — Khóa học không hướng dẫn thiết lập ca trực, cấu hình hạ tầng Kubernetes hay quản trị chi tiết hệ thống giám sát; bạn được trang bị tư duy đo lường các chỉ số cốt lõi để biết hệ thống đang hoạt động đúng.
- **Môi trường thực hành Docker là phần hỗ trợ tùy chọn** — Toàn bộ nguyên lý được truyền tải trọn vẹn qua bài đọc và phân tích code; việc tự chạy kịch bản Docker trên máy cá nhân giúp bạn có thêm trải nghiệm thực tế nhưng không phải điều kiện bắt buộc để tiếp thu bài học.

## Bắt đầu từ đâu

Khóa học mở đầu bằng câu hỏi: sau khi gặp timeout hoặc crash, lệnh bạn vừa gửi sang service bên ngoài đã thực sự chạy hay chưa? Bài học này được đặt ở vị trí xuất phát vì việc nhận diện chính xác khoảng mù thông tin khi dữ liệu rời khỏi bộ nhớ tiến trình là nền tảng bắt buộc trước khi bạn áp dụng bất kỳ giải pháp retry hay phục hồi nào phía sau.

## Chương trình

- Sau timeout hay crash, lệnh đã gửi sang service bên ngoài chạy hay chưa? (miễn phí)

### Kết quả chưa rõ sau timeout và cách retry cho đúng

- Request đã kết thúc nhưng kết quả ở service bên ngoài vẫn chưa rõ (miễn phí)
- Đọc timeline của hai request chạy song song để tìm race condition (miễn phí)
- Client đã timeout nhưng service phía sau vẫn chạy tiếp · quiz
- Lỗi nào nên retry, retry bao nhiêu lần và cách nhau bao lâu · quiz

### Lệnh không chạy hai lần khi retry, không bị mất khi crash

- Idempotency key giúp retry mà không trừ tiền khách hai lần · quiz
- Lệnh chỉ chạy một lần vẫn sai khi dữ liệu đã khác so với lúc duyệt · quiz
- Ghi database và gọi service bên ngoài không nằm chung một transaction · quiz · kit cho agent
- Chặn consumer xử lý một message hai lần khi queue giao lại · quiz

### Lease cho worker, giữ chỗ cho khách, checkpoint cho job

- Lease đã hết hạn mà worker cũ vẫn ghi dữ liệu · quiz
- Giữ chỗ có thời hạn khi khách đang thanh toán, làm bằng Redis hay Postgres · kit cho agent
- Job nhiều bước bị crash giữa chừng thì chạy tiếp từ đâu · quiz

### Phục hồi sau sự cố bằng reconciliation và compensation

- Sau crash, hỏi lại đối tác xem việc đã thật sự xảy ra chưa rồi mới retry · quiz
- Agent gọi lại tool hoàn tiền sau timeout và khách được hoàn tiền hai lần · kit cho agent
- Hoàn tác các bước đã xong khi bước sau thất bại · quiz
- Ghi đúng trạng thái khi gọi cổng thanh toán bị timeout hay nhận webhook trùng · kit cho agent
- Chuyển tiền giữa hai ví mà tổng số dư không bị lệch · kit cho agent
- Sau sự cố, đọc timeline để chọn retry, reconcile hay compensate

### Workflow chạy lâu, từ cron tới durable execution

- Ghi mỗi lượt chạy cron thành một dòng để không lượt nào bị lỡ hay chạy chồng · quiz · kit cho agent
- Lưu hàng chục nghìn timer trong Postgres để worker xử lý chúng đều đặn · quiz
- Đặt hạn chờ cho workflow đang đợi một sự kiện có thể không bao giờ đến · quiz
- Một bảng timer và một worker cho 100.000 đơn sỉ đang chờ thanh toán · kit cho agent
- Ghim version để workflow đang chờ không chạy tiếp bằng code mới sau deploy · quiz
- Tính ngày được bỏ code của một version cũ khi vẫn còn workflow ghim version đó · kit cho agent
- Khi nào nên chuyển từ bảng Postgres sang workflow engine
