Skip to content

ADR-0001. Sinh sổ bất đồng bộ do Kafka điều khiển qua một topic tự lặp

TrườngGiá trị
StatusAccepted
Date2026-03-30
DecidersPhat Nguyen
Supersedes-

Bối cảnh

  • Sinh một sổ vừa chậm vừa dồn dập: lấy dữ liệu + render PDF bằng Typst + render XLSX bằng ExcelJS + mã hoá AES + tải lên S3, dễ mất vài giây mỗi tài liệu và nhân lên cho cả một batch trọn năm.
  • Một request HTTP đồng bộ không thể giữ kết nối mở lâu như vậy, và nếu request gặp sự cố giữa chừng sẽ để lại file S3 dang dở cùng trạng thái không rõ ràng.
  • Việc sinh sổ phải có thể thử lại được và sống sót qua sự cố của worker giữa chừng pipeline.

Quyết định

Chúng ta sẽ tách việc đưa vào hàng đợi khỏi việc thực thi bằng một topic Kafka ledger.generate mà service vừa ghi vào (role api) vừa đọc ra (role worker). Request HTTP trả về ngay với một LedgerJob ở trạng thái PENDING; worker thực thi handleGeneration(ledgerId) và báo tiến độ qua WebSocket.

Consumer chạy với autocommit: false và chỉ commit sau khi tải lên + hoàn tất thành công. Trạng thái job là một máy trạng thái LedgerJob riêng (PENDING → PROCESSING → COMPLETED|REJECTED), được giành quyền xử lý qua một lệnh UPDATE có điều kiện diễn ra như một thao tác duy nhất.

Hệ quả

ƯuNhược
Response HTTP nhanh; phần render lâu nằm ngoài luồng xử lý requestNhất quán sau cùng - client phải hỏi thăm hoặc đăng ký nhận trạng thái
Đưa vào hàng đợi mang tính idempotent trên (merchantId, type, period)Một message đã commit không bao giờ tự phát lại; việc khôi phục cần thử lại tường minh hoặc nhờ vào lượt quét job kẹt
Mở rộng theo chiều ngang bằng cách tăng số consumer / số bản workerNgười vận hành phải hiểu cơ chế tự lặp (không có producer/consumer bên ngoài)
Khôi phục sau sự cố nhờ RecoveryComponent đưa lại các job kẹt vào hàng đợiNhiều thành phần vận động hơn so với một queue BullMQ một chút

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

Phương ánƯuNhượcVì sao loại
Sinh sổ đồng bộ qua HTTPĐơn giản nhấtKết nối giữ lâu, không khôi phục được sau sự cố, để lại file dang dởKhông khả thi cho sổ lớn hoặc xử lý theo batch
Dùng queue BullMQCó sẵn cơ chế thử lại/backoffThêm một lớp hạ tầng nữa; Kafka thì đã có sẵn trong stackTận dụng lại Kafka thay vì thêm cơ chế queue trên Redis
Tự phát lại khi đọc thất bạiTự khắc phụcRủi ro bão message lỗi khi việc parse thất bại một cách tất địnhThử lại thủ công + lượt quét job kẹt có giới hạn thì an toàn hơn

Tham chiếu

  • ledger/src/services/ledger-queue.service.ts (handleEnqueueGeneration)
  • ledger/src/services/ledger-worker.service.ts (handleGeneration)
  • ledger/src/components/kafka.component.ts (consumer autocommit: false)
  • ledger/src/components/recovery.component.ts (lượt quét job kẹt)
  • Generation Pipeline

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