Phần 16 đã đo một điều về prefetch: nó bị bỏ qua hoàn toàn khi autoAck=true. Bài này đo điều còn lại — với ack thủ công, con số đó đáng giá bao nhiêu.
Prefetch là gì
ch.basicQos(100); // tối đa 100 thông điệp chưa ack tại một thời điểm
Nó không phải kích thước lô, cũng không phải bộ đệm. Nó là trần của cột messages_unacknowledged mà phần 4 đo được: broker đẩy thông điệp tới khi consumer đang giữ đủ số đó chưa ack, rồi dừng lại chờ.
Đường cong thông lượng
20 000 thông điệp, ack từng cái, không xử lý gì thêm, trung vị 3 lượt:
| Prefetch | Thông lượng | So với trần |
|---|---|---|
| 1 | 5 991 msg/s | 4% |
| 5 | 22 063 msg/s | 15% |
| 10 | 34 662 msg/s | 24% |
| 50 | 85 685 msg/s | 58% |
| 100 | 112 249 msg/s | 76% |
| 500 | 140 957 msg/s | 96% |
| 1 000 | 135 528 msg/s | 92% |
| không giới hạn | 147 070 msg/s | 100% |
Từ 1 lên 100 là 19 lần. Từ 100 lên không giới hạn chỉ còn 1,3 lần, và sau 500 thì đường cong nằm ngang — chênh lệch giữa 500 và 1 000 nằm trong nhiễu.
Hình dạng đó có lý do đơn giản: với prefetch=1, mỗi thông điệp cần trọn một vòng broker → consumer → ack → broker trước khi cái tiếp theo được gửi. 5 991 msg/s tương đương 0,17 mili giây mỗi vòng — đó là độ trễ mạng, không phải công sức xử lý. Tăng prefetch là cho phép nhiều thông điệp cùng bay, đúng như confirms bất đồng bộ làm ở chiều ngược lại.
Mặt trái không ai đo
Nếu chỉ có bảng trên thì kết luận sẽ là "đặt prefetch thật cao". Nên tôi đo thêm tình huống thật hơn: hai consumer chung một hàng đợi, một nhanh một chậm. Consumer A xử lý 1 ms mỗi thông điệp, consumer B mất 20 ms. Gửi 400 thông điệp:
| Prefetch | Consumer nhanh | Consumer chậm | Tổng thời gian |
|---|---|---|---|
| 1 | 374 | 26 | 0,60 s |
| 10 | 369 | 31 | 0,71 s |
| 100 | 290 | 110 | 2,58 s |
Với prefetch=1, broker chỉ giao khi consumer thật sự rảnh, nên việc tự chảy về phía người làm nhanh — 374 so với 26. Đó là cân bằng tải thật, và nó chỉ có được khi bạn từ chối đẩy trước.
Chọn số nào
Hai bảng trên là hai đầu của cùng một đánh đổi, và câu hỏi quyết định là thời gian xử lý một thông điệp:
| Tình huống | Prefetch | Vì sao |
|---|---|---|
| Xử lý rất nhanh (dưới 1 ms), một consumer | 100–500 | Vòng mạng là cổ chai, bảng một áp dụng |
| Xử lý lâu (hàng chục ms trở lên) | 1–5 | Bảng hai áp dụng; thông lượng bị giới hạn bởi công sức, không phải mạng |
| Nhiều consumer tốc độ khác nhau | thấp | Chỉ prefetch thấp mới cân bằng tải được |
| Thông điệp lớn | thấp | Prefetch nhân với kích thước là bộ nhớ consumer phải giữ |
Quy tắc thực dụng: nếu xử lý một thông điệp mất lâu hơn nhiều so với một vòng mạng, prefetch cao không mua được gì mà chỉ làm phân phối việc tệ đi. Bảng một chỉ đúng khi consumer không làm gì cả — và consumer thật thì luôn làm gì đó.
Số 20 ms trong phép đo thứ hai không phải cực đoan: một lời gọi HTTP hay một truy vấn CSDL đều nằm quanh đó. Nếu consumer của bạn gọi ra ngoài, bạn đang ở bảng hai.
Prefetch cũng là hạn mức bộ nhớ
Thông điệp chưa ack nằm trong bộ nhớ của cả hai phía: client giữ chúng trong hàng đợi nội bộ, broker giữ chúng ở trạng thái unacked. prefetch=10000 với thông điệp 1 MB là 10 GB mà không có gì báo trước. Đây là lý do "không giới hạn" tuy nhanh nhất trong bảng một nhưng không bao giờ nên dùng ngoài phòng thí nghiệm.
Bài sau: redelivery và thông điệp độc — chuyện gì xảy ra với những thông điệp bị trả đi trả lại.
Thử ba mươi giây
# prefetch that su cua tung consumer, va no dang giu bao nhieu
docker exec -u rabbitmq rmq rabbitmqctl -q list_consumers \
queue_name prefetch_count ack_required
prefetch_count=0 nghĩa là không giới hạn — và nếu cột bên cạnh là false thì như phần 16 đã đo, con số đó chẳng có ý nghĩa gì cả.