20 thg 12, 2025·8 phút đọc

Mô hình Outbox trong PostgreSQL cho tích hợp API đáng tin cậy

Tìm hiểu mô hình outbox để lưu sự kiện trong PostgreSQL và gửi tới API bên thứ ba với cơ chế thử lại, đảm bảo thứ tự và loại trùng.

Mô hình Outbox trong PostgreSQL cho tích hợp API đáng tin cậy

Tại sao tích hợp thất bại dù ứng dụng của bạn vẫn hoạt động

Thường thấy một hành động trong ứng dụng báo “thành công” trong khi tích hợp phía sau lại âm thầm thất bại. Ghi vào cơ sở dữ liệu thì nhanh và tin cậy. Lời gọi đến API bên thứ ba thì không. Điều này tạo ra hai thế giới khác nhau: hệ thống của bạn nói thay đổi đã xảy ra, nhưng hệ thống bên ngoài không hề biết.

Ví dụ phổ biến: khách đặt hàng, ứng dụng lưu đơn vào PostgreSQL, rồi cố thông báo cho nhà vận chuyển. Nếu nhà cung cấp bị timeout trong 20 giây và yêu cầu của bạn bỏ cuộc, đơn hàng vẫn có thực nhưng lô hàng không được tạo.

Người dùng trải nghiệm điều này như hành vi khó hiểu, không nhất quán. Sự kiện bị mất trông như “không có gì xảy ra”. Sự kiện trùng lặp trông như “tại sao tôi bị tính phí hai lần?”. Đội hỗ trợ cũng vật lộn vì khó xác định vấn đề do ứng dụng, mạng hay đối tác gây ra.

Việc thử lại giúp nhưng chỉ thử lại không đảm bảo đúng. Nếu bạn thử lại sau timeout, bạn có thể gửi cùng một sự kiện hai lần vì bạn không biết đối tác đã nhận lần đầu hay chưa. Nếu thử lại sai thứ tự, bạn có thể gửi “Order shipped” trước “Order paid.”

Những vấn đề này thường xuất phát từ đồng thời thông thường: nhiều worker xử lý song song, nhiều máy chủ ứng dụng ghi cùng lúc, và các hàng đợi “nỗ lực tốt nhất” khi thời điểm thay đổi dưới tải. Các chế độ thất bại có thể đoán trước: API sập hoặc chậm, mạng rớt, tiến trình crash vào đúng lúc, và việc thử lại tạo ra trùng lặp khi không có gì ép buộc idempotency.

Mô hình outbox tồn tại vì những thất bại này là bình thường.

Mô tả đơn giản về mô hình outbox

Mô hình outbox khá đơn giản: khi ứng dụng của bạn thực hiện một thay đổi quan trọng (ví dụ tạo đơn hàng), nó cũng ghi một bản ghi “sự kiện cần gửi” nhỏ vào một bảng trong cơ sở dữ liệu, trong cùng một giao dịch. Nếu commit cơ sở dữ liệu thành công, bạn biết dữ liệu nghiệp vụ và bản ghi sự kiện tồn tại cùng nhau.

Sau đó, một worker riêng sẽ đọc bảng outbox và gửi các sự kiện đó tới API bên thứ ba. Nếu API chậm, bị xuống hoặc timeout, yêu cầu chính của người dùng vẫn thành công vì nó không chờ cuộc gọi bên ngoài.

Điều này tránh các trạng thái khó xử khi bạn gọi API trong handler yêu cầu:

  • Đơn hàng được lưu, nhưng cuộc gọi API thất bại.
  • Cuộc gọi API thành công, nhưng ứng dụng của bạn crash trước khi lưu đơn.
  • Người dùng thử lại, và bạn gửi cùng một thứ hai lần.

Mô hình outbox chủ yếu giúp xử lý sự kiện bị mất, thất bại một phần (cơ sở dữ liệu ok, API bên ngoài không ok), gửi nhầm hai lần, và thử lại an toàn hơn (bạn có thể thử lại sau mà không phải đoán mò).

Nó không sửa hết mọi thứ. Nếu payload sai, quy tắc nghiệp vụ sai, hoặc API bên thứ ba từ chối dữ liệu, bạn vẫn cần xác thực, xử lý lỗi tốt và cách xem/điều chỉnh các sự kiện thất bại.

Thiết kế bảng outbox trong PostgreSQL

Một bảng outbox tốt thường đơn giản theo ý định. Nó nên dễ ghi, dễ đọc và khó bị sử dụng sai.

Dưới đây là schema cơ bản thực tế bạn có thể điều chỉnh:

create table outbox_events (
  id            bigserial primary key,
  aggregate_id  text not null,
  event_type    text not null,
  payload       jsonb not null,
  status        text not null default 'pending',
  created_at    timestamptz not null default now(),
  available_at  timestamptz not null default now(),
  attempts      int not null default 0,
  locked_at     timestamptz,
  locked_by     text,
  meta          jsonb not null default '{}'::jsonb
);

Chọn ID

Dùng bigserial (hoặc bigint) giữ thứ tự đơn giản và chỉ mục nhanh. UUID tuyệt vời cho độ duy nhất giữa các hệ thống, nhưng chúng không sắp theo thứ tự tạo, điều này có thể làm polling kém dự đoán và chỉ mục nặng hơn.

Một thỏa hiệp phổ biến là: giữ idbigint để đảm bảo thứ tự, và thêm event_uuid riêng nếu bạn cần định danh ổn định để chia sẻ giữa các dịch vụ.

Các chỉ mục quan trọng

Worker của bạn sẽ truy vấn cùng các mẫu suốt ngày. Hầu hết hệ thống cần:

  • Một chỉ mục như (status, available_at, id) để lấy các sự kiện pending tiếp theo theo thứ tự.
  • Một chỉ mục trên (locked_at) nếu bạn định hết hạn các khoá cũ.
  • Một chỉ mục như (aggregate_id, id) nếu đôi khi bạn gửi theo aggregate theo thứ tự.

Giữ payload ổn định

Giữ payload nhỏ và dự đoán được. Lưu những gì người nhận thực sự cần, không phải toàn bộ hàng của bạn. Thêm một phiên bản rõ ràng (ví dụ trong meta) để bạn có thể thay đổi trường an toàn.

Dùng meta cho routing và ngữ cảnh debug như tenant ID, correlation ID, trace ID và khoá dedup. Ngữ cảnh phụ này sẽ hữu ích khi hỗ trợ cần trả lời “chuyện gì đã xảy ra với đơn hàng này?”.

Cách lưu sự kiện an toàn cùng với ghi nghiệp vụ

Quy tắc quan trọng nhất rất đơn giản: ghi dữ liệu nghiệp vụ và sự kiện outbox trong cùng một giao dịch cơ sở dữ liệu. Nếu transaction commit, cả hai tồn tại. Nếu rollback, không cái nào tồn tại.

Ví dụ: khách đặt đơn. Trong một transaction bạn insert hàng đơn, các mục đơn, và một hàng outbox như order.created. Nếu bất kỳ bước nào thất bại, bạn không muốn một sự kiện “created” bị thoát ra thế giới.

Một event hay nhiều event?

Bắt đầu với một event cho mỗi hành động nghiệp vụ khi có thể. Nó dễ hiểu hơn và rẻ hơn để xử lý. Chia thành nhiều event chỉ khi các consumer khác nhau thực sự cần thời điểm hoặc payload khác nhau (ví dụ order.created cho fulfillment và payment.requested cho billing). Sinh nhiều event cho một lần nhấn chuột làm tăng thử lại, đau đầu về thứ tự và xử lý trùng lặp.

Nên lưu payload gì?

Bạn thường chọn giữa:

  • Snapshot: lưu các trường chính tại thời điểm hành động (tổng đơn, tiền tệ, customer ID). Điều này tránh đọc thêm sau này và giữ thông điệp ổn định.
  • Reference ID: chỉ lưu order ID và để worker tải chi tiết sau. Giữ outbox nhỏ, nhưng thêm các lần đọc và có thể thay đổi nếu đơn bị chỉnh sửa.

Một điểm cân bằng thực tế là các định danh cộng với một snapshot nhỏ các giá trị quan trọng. Điều này giúp người nhận hành động nhanh và giúp bạn debug.

Giữ phạm vi transaction chặt. Đừng gọi API bên thứ ba trong cùng một transaction.

Gửi sự kiện tới API bên thứ ba: vòng lặp worker

Tạo outbox trong vài phút
Mô hình bảng outbox trong Data Designer và gửi sự kiện mà không chặn thanh toán.
Start Building

Khi sự kiện đã ở trong outbox, bạn cần một worker đọc chúng và gọi API bên thứ ba. Đây phần biến mẫu thành tích hợp đáng tin cậy.

Polling thường là lựa chọn đơn giản nhất. LISTEN/NOTIFY có thể giảm độ trễ, nhưng nó thêm thành phần vận hành và vẫn cần cơ chế dự phòng khi notification bị bỏ lỡ hoặc worker khởi động lại. Với hầu hết đội, polling đều đặn với lô nhỏ dễ vận hành và gỡ lỗi hơn.

Chiếm hàng an toàn

Worker nên chiếm hàng để hai worker không xử lý cùng một event cùng lúc. Trong PostgreSQL, cách phổ biến là select một lô dùng khoá hàng và SKIP LOCKED, rồi đánh dấu chúng đang xử lý.

Luồng trạng thái thực tế thường là:

  • pending: sẵn sàng gửi
  • processing: bị khoá bởi một worker (dùng locked_bylocked_at)
  • sent: đã gửi thành công
  • failed: dừng sau số lần thử tối đa (hoặc chuyển sang xem xét thủ công)

Giữ kích thước lô nhỏ để nhẹ nhàng với cơ sở dữ liệu. Lô từ 10 đến 100 hàng, chạy mỗi 1 đến 5 giây, là điểm khởi đầu phổ biến.

Khi cuộc gọi thành công, đánh dấu hàng là sent. Khi thất bại, tăng attempts, đặt available_at vào thời điểm tương lai (backoff), xoá khoá và trả về pending.

Ghi log hữu ích (không lộ bí mật)

Log tốt làm cho lỗi có thể hành động. Log id outbox, loại sự kiện, tên đích, số lần thử, thời gian và mã HTTP hoặc lớp lỗi. Tránh log body yêu cầu, header auth và phản hồi đầy đủ. Nếu cần correlation, lưu request ID an toàn hoặc hash thay vì payload thô.

Quy tắc thứ tự hoạt động trong hệ thống thực tế

Triển khai tích hợp có thể gỡ lỗi
Kết nối thanh toán, nhắn tin và API bên ngoài đồng thời giữ được khả năng kiểm tra lỗi tích hợp.
Get Started

Nhiều đội bắt đầu với “gửi sự kiện theo cùng thứ tự chúng ta tạo”. Vấn đề là “cùng thứ tự” hiếm khi mang tính toàn cục. Nếu bạn ép một hàng đợi toàn cục, một khách hàng chậm hoặc API flaky có thể làm tắc mọi người.

Quy tắc thực tế là: giữ thứ tự theo nhóm, không phải toàn hệ thống. Chọn khoá nhóm phù hợp với cách thế giới bên ngoài nghĩ về dữ liệu của bạn, như customer_id, account_id hoặc aggregate_id như order_id. Sau đó bảo đảm thứ tự bên trong mỗi nhóm trong khi gửi nhiều nhóm song song.

Worker song song mà không phá vỡ thứ tự

Chạy nhiều worker, nhưng đảm bảo hai worker không xử lý cùng một nhóm cùng lúc. Cách thường làm là luôn gửi event pending sớm nhất cho một aggregate_id và cho phép song song giữa các aggregate khác nhau.

Giữ quy tắc chiếm đơn giản:

  • Chỉ gửi event pending sớm nhất cho mỗi nhóm.
  • Cho phép song song giữa các nhóm, không phải trong cùng nhóm.
  • Chiếm một event, gửi nó, cập nhật trạng thái rồi tiếp tục.

Khi một event chặn phần còn lại

Sớm muộn gì, một “poison” event sẽ thất bại trong nhiều giờ (payload sai, token bị thu hồi, nhà cung cấp sập). Nếu bạn bắt buộc thứ tự theo nhóm, các event sau trong nhóm đó phải chờ, nhưng các nhóm khác nên tiếp tục.

Một thỏa hiệp khả thi là giới hạn số lần thử cho mỗi event. Sau đó, đánh dấu nó failed và tạm dừng chỉ nhóm đó cho tới khi ai đó sửa nguyên nhân gốc. Cách này giữ một khách hàng hỏng không làm chậm mọi người.

Thử lại mà không làm tình hình tồi tệ hơn

Thử lại là nơi một thiết lập outbox tốt trở nên đáng tin cậy hoặc ồn ào. Mục tiêu: thử lại khi có khả năng thành công và dừng nhanh khi không có hy vọng.

Dùng exponential backoff và ngưỡng cứng. Ví dụ: 1 phút, 2 phút, 4 phút, 8 phút, rồi dừng (hoặc tiếp tục với delay tối đa như 15 phút). Luôn đặt số lần thử tối đa để một event xấu không làm tắc hệ thống mãi mãi.

Không phải lỗi nào cũng nên thử lại. Giữ quy tắc rõ ràng:

  • Thử lại: timeout mạng, reset kết nối, DNS, và HTTP 429 hoặc 5xx.
  • Không thử lại: HTTP 400 (yêu cầu sai), 401/403 (xác thực), 404 (endpoint sai), hoặc lỗi xác thực bạn có thể phát hiện trước khi gửi.

Lưu trạng thái thử lại trên hàng outbox. Tăng attempts, đặt available_at cho lần thử tiếp theo, và ghi tóm tắt lỗi ngắn an toàn (mã trạng thái, lớp lỗi, thông điệp rút gọn). Đừng lưu payload đầy đủ hay dữ liệu nhạy cảm trong trường lỗi.

Giới hạn tần suất (rate limits) cần xử lý đặc biệt. Nếu nhận HTTP 429, tôn trọng Retry-After khi có. Nếu không, backoff mạnh hơn để tránh bão thử lại.

Cơ bản về loại trùng và idempotency

Xử lý thứ tự theo cách thực dụng
Giữ thứ tự sự kiện theo khách hàng hoặc đơn hàng mà không làm chậm toàn bộ hệ thống.
Create Project

Nếu bạn xây tích hợp đáng tin cậy, giả định cùng một event có thể được gửi hai lần. Worker có thể crash sau cuộc gọi HTTP nhưng trước khi ghi nhận thành công. Timeout có thể che giấu một thành công. Một lần thử lại có thể chồng lên lần đầu chậm.

Mô hình outbox giảm sự kiện bị mất, nhưng không ngăn trùng lặp tự động.

Cách an toàn nhất là idempotency: gửi lại nhiều lần tạo cùng kết quả như gửi một lần. Khi gọi API bên thứ ba, kèm theo idempotency key ổn định cho event và đích đó. Nhiều API hỗ trợ header; nếu không, đặt khoá trong body.

Một khoá đơn giản là ghép đích và event ID. Với event ID evt_123, luôn dùng thứ gì đó như destA:evt_123.

Ở phía bạn, ngăn trùng lặp bằng cách giữ một bảng log gửi ra và áp dụng ràng buộc duy nhất như (destination, event_id). Ngay cả khi hai worker tranh chấp, chỉ một có thể tạo bản ghi “chúng tôi đang gửi cái này”.

Webhook cũng trùng lặp

Nếu bạn nhận callback webhook (như “giao hàng xác nhận” hoặc “trạng thái cập nhật”), xử lý giống nhau. Nhà cung cấp sẽ thử lại, và bạn có thể nhận cùng payload nhiều lần. Lưu ID webhook đã xử lý, hoặc tính hash ổn định từ message ID của nhà cung cấp và từ chối lặp lại.

Giữ dữ liệu bao lâu

Giữ hàng outbox cho tới khi bạn ghi nhận thành công (hoặc một thất bại cuối cùng bạn chấp nhận). Giữ delivery logs lâu hơn vì chúng là nhật ký kiểm toán khi ai đó hỏi “Chúng ta đã gửi chưa?”.

Cách phổ biến:

  • Outbox rows: xóa hoặc lưu trữ sau khi thành công cộng thêm một cửa sổ an toàn ngắn (vài ngày).
  • Delivery logs: giữ vài tuần hoặc vài tháng, theo quy định và nhu cầu hỗ trợ.
  • Idempotency keys: giữ ít nhất bằng thời gian retry có thể xảy ra (và lâu hơn cho webhook).

Các bước thực hiện mô hình outbox

Quyết định bạn sẽ publish gì. Giữ sự kiện nhỏ, tập trung và dễ replay sau. Quy tắc tốt là một sự thật nghiệp vụ mỗi event, với đủ dữ liệu để người nhận hành động.

Xây nền tảng

Chọn tên event rõ ràng (ví dụ order.created, order.paid) và version payload (như v1, v2). Version giúp bạn thêm trường sau này mà không phá vỡ consumer cũ.

Tạo bảng outbox PostgreSQL và thêm chỉ mục cho các truy vấn worker thường dùng, đặc biệt (status, available_at, id).

Cập nhật luồng ghi để thay đổi nghiệp vụ và insert outbox xảy ra trong cùng một transaction. Đó là đảm bảo cốt lõi.

Thêm phần gửi và điều khiển

Kế hoạch triển khai đơn giản:

  • Định nghĩa loại event và phiên bản payload bạn hỗ trợ lâu dài.
  • Tạo bảng outbox và các chỉ mục.
  • Insert hàng outbox kèm với thay đổi dữ liệu chính.
  • Xây worker chiếm hàng, gửi tới API bên thứ ba, rồi cập nhật trạng thái.
  • Thêm lập lịch thử lại với backoff và trạng thái failed khi vượt quá số lần thử.

Thêm các chỉ số cơ bản để bạn nhận ra vấn đề sớm: lag (tuổi của event chưa gửi cũ nhất), tốc độ gửi, và tỷ lệ thất bại.

Ví dụ đơn giản: gửi sự kiện đơn hàng tới dịch vụ ngoài

Tạo công cụ vận hành mở rộng
Tạo công cụ vận hành nội bộ và cổng khách hàng phụ thuộc vào việc gửi sự kiện đáng tin cậy.
Build With No Code

Khách hàng đặt đơn trong ứng dụng. Hai việc cần xảy ra ngoài hệ thống: nhà cung cấp thanh toán phải charge thẻ, và nhà vận chuyển phải tạo lô hàng.

Với mô hình outbox, bạn không gọi các API đó trong request checkout. Thay vào đó, bạn lưu đơn và một event outbox trong cùng một transaction PostgreSQL, nên bạn không bao giờ rơi vào trạng thái “đơn lưu nhưng không thông báo” (hoặc ngược lại).

Hàng outbox điển hình cho event đơn hàng có thể bao gồm aggregate_id (ID đơn), event_type như order.created, và payload JSONB với tổng tiền, các mục và địa chỉ giao.

Một worker sẽ nhặt các hàng pending và gọi dịch vụ ngoài (theo thứ tự xác định hoặc phát ra các event riêng như payment.requestedshipment.requested). Nếu một nhà cung cấp xuống, worker ghi lại lần thử, lập lịch thử tiếp theo bằng cách đẩy available_at vào tương lai, rồi tiếp tục. Đơn hàng vẫn tồn tại, và event sẽ được thử lại sau mà không chặn checkout mới.

Thứ tự thường là “theo đơn” hoặc “theo khách hàng.” Đảm bảo các event cùng aggregate_id được xử lý lần lượt để order.paid không bao giờ đến trước order.created.

Loại trùng giữ bạn khỏi charge hai lần hoặc tạo hai lô hàng. Gửi idempotency key khi nhà cung cấp hỗ trợ, và giữ bản ghi giao hàng đích để thử lại sau timeout không gây hành động thứ hai.

Kiểm tra nhanh trước khi đưa vào sản xuất

Giữ tùy chọn xuất về mã nguồn
Xuất mã Go, Vue3 và Kotlin hoặc SwiftUI thực tế khi bạn cần kiểm soát hoàn toàn.
Generate Code

Trước khi tin tưởng một tích hợp điều chuyển tiền, thông báo khách hay đồng bộ dữ liệu, thử các cạnh: crash, retry, duplicate, và nhiều worker.

Các kiểm tra hay bắt các lỗi phổ biến:

  • Xác nhận hàng outbox được tạo trong cùng transaction với thay đổi nghiệp vụ.
  • Xác minh sender an toàn chạy nhiều instance. Hai worker không nên gửi cùng một event cùng lúc.
  • Nếu thứ tự quan trọng, định nghĩa quy tắc bằng một câu và thực thi nó với khoá ổn định.
  • Với mỗi đích, quyết định cách ngăn trùng lặp và cách chứng minh “chúng ta đã gửi nó”.
  • Định nghĩa lối thoát: sau N lần thử, chuyển event sang failed, giữ tóm tắt lỗi cuối cùng và cung cấp hành động reprocess đơn giản.

Một thực tế: Stripe có thể chấp nhận yêu cầu nhưng worker của bạn crash trước khi lưu thành công. Nếu không có idempotency, thử lại có thể gây hành động kép. Với idempotency cộng bản ghi giao hàng, thử lại sẽ an toàn.

Bước tiếp theo: triển khai mà không làm gián đoạn ứng dụng

Rollout là nơi các dự án outbox thường thành công hoặc vỡ. Bắt đầu nhỏ để nhìn hành vi thực tế mà không đặt toàn bộ lớp tích hợp vào rủi ro.

Bắt đầu với một tích hợp và một loại event. Ví dụ, chỉ gửi order.created tới một API vendor trong khi phần còn lại giữ nguyên. Điều này cho bạn cơ sở sạch để đo throughput, độ trễ và tỷ lệ thất bại.

Làm cho vấn đề hiện rõ sớm. Thêm dashboard và cảnh báo cho outbox lag (có bao nhiêu event đang chờ, và event cũ nhất bao nhiêu tuổi) và tỷ lệ thất bại (bao nhiêu bị kẹt trong retry). Nếu bạn có thể trả lời “hiện tại chúng ta đang bị chậm không?” trong 10 giây, bạn sẽ phát hiện lỗi trước khi người dùng thấy.

Có kế hoạch reprocess an toàn trước sự cố đầu tiên. Quyết định “reprocess” nghĩa là gì: thử lại cùng payload, xây lại payload từ dữ liệu hiện thời, hay gửi để xem xét thủ công. Ghi lại trường hợp nào an toàn gửi lại và trường hợp nào cần kiểm tra con người.

Nếu bạn xây bằng nền tảng no-code như AppMaster (appmaster.io), cấu trúc vẫn giống: ghi dữ liệu nghiệp vụ và một hàng outbox cùng nhau trong PostgreSQL, rồi chạy một tiến trình backend riêng để gửi, thử lại và đánh dấu event là sent hoặc failed.

Câu hỏi thường gặp

When should I use the outbox pattern instead of calling the API directly?

Sử dụng mô hình outbox khi một hành động của người dùng cập nhật cơ sở dữ liệu của bạn cần kích hoạt công việc ở hệ thống khác. Nó hữu ích nhất khi timeout, mạng không ổn định hoặc nhà cung cấp bên thứ ba có thể tạo ra tình huống “đã lưu trong hệ thống chúng ta, nhưng họ lại không nhận được”.

Why does the outbox insert need to be in the same transaction as the business write?

Ghi cả hàng business và hàng outbox trong cùng một giao dịch cơ sở dữ liệu cho bạn một đảm bảo rõ ràng: hoặc cả hai tồn tại hoặc không cái nào tồn tại. Điều này ngăn các thất bại một phần như “cuộc gọi API thành công nhưng đơn hàng chưa được lưu” hoặc “đơn hàng đã lưu nhưng cuộc gọi API chưa xảy ra”.

What fields should an outbox table include to be practical?

Một mặc định tốt là các trường id, aggregate_id, event_type, payload, status, created_at, available_at, attempts, và các trường khoá như locked_atlocked_by. Những trường này giúp việc gửi, lập lịch thử lại và xử lý đồng thuận đơn giản mà không làm phức tạp bảng.

What indexes matter most for an outbox table in PostgreSQL?

Một chỉ mục phổ biến là (status, available_at, id) để worker có thể nhanh chóng lấy lô sự kiện sẵn sàng gửi theo thứ tự. Chỉ thêm chỉ mục khác khi bạn thực sự truy vấn theo các trường đó, vì chỉ mục phụ làm chậm các lần insert.

Should my worker poll the outbox table or use LISTEN/NOTIFY?

Polling là cách đơn giản và dễ đoán cho hầu hết đội. Bắt đầu với các lô nhỏ và khoảng thời gian ngắn, sau đó tinh chỉnh dựa trên tải và độ trễ; bạn có thể thêm tối ưu sau, nhưng vòng lặp đơn giản dễ gỡ lỗi khi có sự cố.

How do I prevent two workers from sending the same outbox event?

Chiếm hàng bằng khoá ở mức hàng (row-level locks) để hai worker không thể xử lý cùng một event cùng lúc, thường dùng SKIP LOCKED. Sau đó đánh dấu hàng là processing với thời gian khoá và ID worker, gửi, rồi đánh dấu sent hoặc trả về pending với available_at trong tương lai.

What’s the safest retry strategy for outbox deliveries?

Sử dụng backoff mũ (exponential backoff) với giới hạn cố định về số lần thử, và chỉ thử lại các lỗi có khả năng tạm thời. Timeout, lỗi mạng và HTTP 429/5xx là ứng cử viên nên thử lại; lỗi xác thực hoặc 400/4xx do dữ liệu sai thường là cuối cùng cho đến khi bạn sửa cấu hình hoặc dữ liệu.

Does the outbox pattern guarantee exactly-once delivery?

Giả định rằng trùng lặp vẫn có thể xảy ra, đặc biệt nếu worker chết sau khi gọi HTTP nhưng trước khi ghi nhận thành công. Dùng idempotency key cố định theo đích và theo sự kiện, và giữ một bảng ghi giao hàng (delivery log) với ràng buộc duy nhất để ngay cả khi hai worker tranh chấp, chỉ một gửi thực sự được ghi nhận.

How do I handle ordering without slowing down the whole system?

Ưu tiên giữ thứ tự trong một nhóm thay vì toàn hệ thống. Dùng khoá nhóm như aggregate_id (ID đơn hàng) hoặc customer_id, xử lý mỗi nhóm một sự kiện tại một thời điểm, và cho phép song song giữa các nhóm khác nhau để một khách hàng chậm không làm tắc mọi người.

What should I do with a “poison” event that keeps failing?

Đánh dấu là failed sau số lần thử tối đa, giữ tóm tắt lỗi ngắn gọn và an toàn, và dừng xử lý các sự kiện sau trong cùng nhóm cho đến khi ai đó sửa nguyên nhân. Cách này chứa vùng ảnh hưởng và ngăn việc thử lại vô tận trong khi vẫn cho phép các nhóm khác tiếp tục.

Dễ dàng bắt đầu
Tạo thứ gì đó tuyệt vời

Thử nghiệm với AppMaster với gói miễn phí.
Khi bạn sẵn sàng, bạn có thể chọn đăng ký phù hợp.

Bắt đầu