Queues & Streams: capacity, delivery và backpressure
Queue hấp thụ burst hữu hạn và decouple thời gian giữa producer với consumer; nó không tạo thêm downstream capacity. Vì vậy thiết kế async phải đi cùng capacity model, delivery semantics, fan-out strategy và operational signals.
1. Khi nào nên dùng async?
Async phù hợp cho long-running work, fan-out, công việc bị giới hạn bởi provider hoặc các workflow có thể chấp nhận eventual consistency. Thay vì giữ request mở đến khi toàn bộ công việc hoàn tất, user-facing API có thể trả trạng thái accepted kèm operation ID và expose một cơ chế để client kiểm tra status.
Tuy nhiên không phải mọi bước đều nên chuyển sang async. Các bước cần thỏa immediate invariant hoặc ví dụ như payment authorization có thể vẫn phải giữ synchronous để caller nhận kết quả quyết định ngay.
| Tình huống | Hướng thiết kế từ source | Lý do chính |
|---|---|---|
| Long-running work | Async | Không giữ request lifecycle quá lâu. |
| Một event cần gửi đến nhiều nơi | Async fan-out | Decouple producer khỏi từng consumer. |
| Provider bị rate limit | Async | Cho phép xếp hàng và xử lý theo nhịp provider. |
| Eventual consistency chấp nhận được | Async | Completion có thể xảy ra sau response ban đầu. |
| Immediate invariant / payment authorization | Có thể vẫn sync | Kết quả phải được xác nhận trong request hiện tại. |
2. Capacity: queue chỉ trì hoãn failure nếu hệ thống thiếu capacity
Quan hệ cốt lõi là backlog growth = arrival rate − service rate. Khi arrival rate lớn hơn service rate, backlog tăng. Queue có thể hấp thụ burst hữu hạn, nhưng nếu arrival dài hạn vẫn vượt service thì queue chỉ trì hoãn failure, không giải quyết nguyên nhân thiếu capacity.
Khi size queue hoặc stream retention, source yêu cầu xét ít nhất các yếu tố: burst duration, payload, retention và drain RTO. Drain RTO trả lời câu hỏi: sau một burst hoặc outage, hệ thống phải mất bao lâu để xử lý hết backlog và quay về trạng thái bình thường.
Capacity checklist
- Arrival rate hiện tại và trong burst là bao nhiêu?
- Service rate bền vững của consumer/downstream là bao nhiêu?
- Burst có thể kéo dài bao lâu?
- Payload lớn đến mức nào và retention yêu cầu bao lâu?
- Sau burst, drain RTO cho phép là bao lâu?
- Khi queue chạm bound, hệ thống sẽ shed load theo policy nào?
3. Delivery semantics, retry và ordering
Với at-least-once, consumer phải được thiết kế idempotent vì message có thể được giao lại. Ordering nên được định nghĩa theo scope thực sự cần thiết; source nêu hướng phổ biến là ordering theo entity key thay vì giả định global ordering.
Retry phải bounded và có backoff. Message không thể xử lý tiếp sau số lần thử hợp lý cần được đưa vào DLQ, và DLQ phải có owner cùng quy trình replay. Nếu chỉ tạo DLQ mà không có ai chịu trách nhiệm phân loại và replay thì lỗi chỉ bị chuyển sang một nơi khác.
Với tuyên bố exactly-once, scope guarantee phải được nói rõ. Không nên dùng cụm từ này như một guarantee mơ hồ cho toàn bộ workflow. Khi business transaction vừa ghi database vừa publish message, outbox là cơ chế source đề cập để đóng dual-write gap.
| Chủ đề | Yêu cầu từ source |
|---|---|
| At-least-once | Consumer idempotent. |
| Ordering | Định nghĩa theo entity key khi cần. |
| Retry | Bounded retry + backoff. |
| Poison message | Đưa vào DLQ; có owner và replay process. |
| Exactly-once | Scope guarantee phải rõ. |
| Dual write | Dùng outbox để đóng gap giữa state change và publish. |
4. Fan-out: push-at-write, pull-at-read và hybrid
Push-at-write làm read nhanh hơn vì kết quả đã được phân phối trước, đổi lại là write amplification. Pull-at-read đơn giản hóa write nhưng làm read đắt hơn vì phải tổng hợp khi đọc. Source khuyến nghị nghĩ theo hướng hybrid cho celebrity hoặc hot users, nơi fan-out toàn bộ ở write time có thể quá tốn kém.
Notification design còn bị chi phối bởi provider rate limit và per-recipient preferences. Nghĩa là fan-out không chỉ là “nhân bản message”; hệ thống còn phải tôn trọng tốc độ provider và quyết định ai thực sự nên nhận notification.
5. Operational signals: đừng nhìn queue depth một mình
Queue depth chỉ cho biết có bao nhiêu item đang nằm chờ, nhưng thiếu context về tốc độ đến, tốc độ xử lý và độ cũ của backlog. Source yêu cầu theo dõi đồng thời các signal sau:
- Age of oldest: item cũ nhất đã chờ bao lâu.
- Lag/backlog per partition/queue: backlog phân bố ở đâu, có skew hay không.
- Arrival rate và service rate: backlog đang tăng hay có khả năng được drain.
- Attempts: retry có tăng bất thường hay không.
- DLQ: poison/permanent failure đang tích tụ thế nào.
- End-to-end completion latency: từ khi work được tạo đến khi business workflow hoàn tất.
- Stuck workflows: các workflow không tiến triển dù queue vẫn hoạt động.
6. Review checklist nhanh
- Flow này thực sự cần async hay có invariant buộc phải sync?
- Arrival rate, service rate, burst duration và drain RTO đã được lượng hóa chưa?
- Queue có bound và load shedding policy chưa?
- Consumer at-least-once có idempotent không?
- Ordering scope có gắn với entity key rõ ràng không?
- Retry có bounded + backoff; DLQ có owner + replay không?
- Nếu nói exactly-once, guarantee áp dụng ở scope nào?
- Nếu có DB write + publish, dual-write gap đã được xử lý bằng outbox chưa?
- Fan-out dùng push, pull hay hybrid; provider rate limit và preferences nằm ở đâu?
- Dashboard có age, lag/backlog, rates, attempts, DLQ, end-to-end latency và stuck workflows chưa?