Skip to content

ADR-0003. Phát hành bất đồng bộ trên BullMQ 3 phân vùng với hàm băm order xác định

TrườngGiá trị
StatusAccepted
Date2026-04-15
DecidersPhat Nguyen
Supersedes-

Bối cảnh

  • Phát hành qua provider là I/O-bound (HTTP tới VNIS/VNPAY/T-VAN) và có thể lỗi tạm thời - không được chặn Kafka consumer hay request REST.
  • Cùng một order không bao giờ được phát hành đồng thời trên nhiều worker (rủi ro phát hành kép).
  • Thông lượng phải mở rộng theo chiều ngang trong khi vẫn giữ tuần tự hoá theo từng order.
  • Retry cần backoff có giới hạn; lỗi vĩnh viễn (4xx) không được retry.

Quyết định

Chúng ta sẽ phát hành hoá đơn bất đồng bộ qua BullMQ với 3 phân vùng cho mỗi loại queue (issuance, claim-expiry). Một order được định tuyến tới phân vùng bằng getPartitionByKey(orderId) - hàm Java-hashCode mod 3 mang tính xác định - nên cùng một order luôn rơi vào cùng phân vùng. Job phát hành dùng jobId = orderId để đảm bảo idempotent.

Chính sách retry đến từ InvoiceProviderConfig.retryMetadata (mặc định maxRetryCount = 3, retryDelayMinutes = [5, 15, 60]). Lỗi 4xx vĩnh viễn (≠429) lập tức chuyển sang FAILED; job cạn lượt retry hoặc rơi vào DLQ sẽ lật hoá đơn sang FAILED và ghi một hàng audit. Mức concurrency của worker phát hành lấy từ APP_ENV_INVOICE_ISSUANCE_WORKER_CONCURRENCY (mặc định 10); claim-expiry cố định ở mức 3.

Hệ quả

ƯuNhược
Tuần tự hoá theo từng order mà không cần khoá toàn cụcSố phân vùng (3) là hằng số cố định
Mở rộng theo chiều ngang qua tham số concurrencyTái cân bằng phân vùng sau này sẽ đổi ánh xạ order→partition
Backoff có giới hạn; lỗi vĩnh viễn thất bại nhanhTrạng thái retry nằm trên hàng hoá đơn (retryCount, metadata)
Claim-expiry là job có độ trễ (không cần polling)Xử lý DLQ làm riêng theo từng loại worker

Phương án đã cân nhắc

Phương ánƯuNhượcVì sao loại
Phát hành đồng bộ trong handler KafkaĐơn giản nhấtChặn consumer; không cô lập retryLỗi provider tạm thời làm nghẽn pipeline
Một queue (không phân vùng)Định tuyến đơn giảnKhông có tính gắn kết theo order; concurrency gây rủi ro phát hành képMất bảo đảm tuần tự hoá
Khoá phân tán cho mỗi orderLoại trừ lẫn nhau một cách tường minhTranh chấp khoá + rủi ro rò rỉ khi gặp lỗiBăm theo phân vùng đạt được điều này mà không tốn thêm chi phí
Cron-poll chỉ cho hoá đơn đang chờKhông cần hạ tầng queueĐộ trễ cao với mode REAL_TIMEChỉ giữ cho mode SCHEDULED

Tham chiếu

  • src/common/queues.ts (InvoiceQueuePartitions, getPartitionByKey, các định nghĩa)
  • src/components/invoice-queue/component.ts (queue/worker phân vùng, DLQ)
  • src/services/invoice-issuance-queue.service.ts (enqueueIssuance, _handleIssuanceFailure)
  • Xem thêm: API Events §3

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.