AMQP 0-9-1 có sẵn transaction, ra đời trước publisher confirms, và tài liệu chính thức khuyên đừng dùng. Bài này đo xem lời khuyên đó đáng giá bao nhiêu.

Transaction làm gì

ch.txSelect();                       // bật cho channel này
ch.basicPublish("", "tx", props, body);
ch.basicPublish("", "tx", props, body);
ch.txCommit();                       // hoặc txRollback()

Thông điệp trong transaction chưa được nhìn thấy cho tới lúc commit:

sau khi gửi 5, TRƯỚC commit : hàng đợi có 0
sau txRollback              : hàng đợi có 0
gửi lại 5 rồi txCommit      : hàng đợi có 5

Đây là điều confirms không làm được: confirms cho biết broker đã nhận, nhưng thông điệp vào hàng đợi ngay lập tức, không có cách nào rút lại.

Đo cạnh confirms

Cùng phép đo với phần trước — 20 000 thông điệp persistent, trung vị 3 lượt:

Cách gom nhóm Transaction Publisher confirms
Từng thông điệp 2 405 msg/s 2 453 msg/s
Lô 100 60 193 msg/s 73 575 msg/s
Lô 1 000 114 913 msg/s 150 482 msg/s
Bất đồng bộ không có 167 617 msg/s

Ở lô nhỏ nhất, hai bên gần bằng nhau — cả hai đều phải chờ một vòng mạng. Lô càng lớn, transaction càng tụt lại: 18% ở lô 100, 24% ở lô 1 000.

Nhưng con số quan trọng nhất là ô trống ở dòng cuối. Transaction không có chế độ bất đồng bộ. txCommit luôn chặn cho tới khi broker trả lời, nên bạn không bao giờ có nhiều lô đang bay cùng lúc. Cách nhanh nhất của transaction (114 913) vẫn chậm hơn cách nhanh nhất của confirms (167 617) 1,5 lần — và khoảng cách đó không có cách nào thu hẹp.

Đó là câu trả lời cho "vì sao tài liệu khuyên tránh": không phải vì transaction chậm thảm hại, mà vì trần của nó thấp hơn và bạn không có nút nào để đẩy lên.

Rollback không trả thông điệp về hàng đợi

Transaction cũng bao trùm cả basicAck, và đây là chỗ tôi đọc sai kết quả trong lần đo đầu. Lấy ba thông điệp, ack cả ba trong transaction, rồi rollback:

Thời điểm ready unacked
Vừa gửi 3 3 0
Đã lấy và ack 3, chưa commit 0 3
Sau txRollback 0 3

Ack bị huỷ — đúng như mong đợi. Nhưng thông điệp không quay về trạng thái ready; chúng vẫn nằm unacked, vẫn thuộc về channel đó. Rollback không phải requeue.

Muốn chúng quay lại hàng đợi thì phải basicNack(requeue=true), hoặc đóng channel như phần 4 đã đo. Nếu tôi chỉ nhìn cột messages như lần đầu thì đã kết luận ngược hẳn — getMessageCount() chỉ đếm ready, không đếm unacked.

Không trộn được với confirms

txSelect rồi confirmSelect -> 406 PRECONDITION_FAILED - cannot switch from tx to confirm mode
confirmSelect rồi txSelect -> 406 PRECONDITION_FAILED - cannot switch from confirm to tx mode

Một channel chọn một chế độ, và chọn rồi thì không đổi được. Cả hai lỗi đều đóng channel. Muốn dùng cả hai thì phải hai channel — mà phần 3 cho thấy channel rẻ, nên đó không phải vấn đề.

Transaction này không phải transaction bạn đang nghĩ

Đây là hiểu nhầm đắt nhất. txCommit chỉ nguyên tử trong phạm vi broker. Nó không liên quan gì tới transaction của cơ sở dữ liệu:

db.commit();          // ← thành công
ch.txCommit();        // ← chết ở đây: CSDL đã ghi, thông điệp thì không

Đảo thứ tự cũng không cứu được — chỉ đổi sang kịch bản ngược lại. Không có cách nào làm hai hệ thống commit cùng nhau bằng cơ chế này, và đó là lý do mẫu outbox tồn tại (bài 23).

Khi nào vẫn dùng

Khi bạn cần rút lại thông điệp đã gửi trong cùng một luồng xử lý — ví dụ gửi vài thông điệp rồi phát hiện dữ liệu đầu vào hỏng và muốn không cái nào lọt ra. Confirms không làm được việc đó.

Ngoài ra thì dùng confirms. Nếu bạn đang dùng transaction chỉ để "chắc chắn broker đã nhận", bạn đang trả 1,5 lần thông lượng cho một tính năng bạn không dùng tới.

Bài sau: prefetch — một con số duy nhất đổi thông lượng 19 lần, và đổi cả thời gian hoàn thành theo hướng ngược lại.

Thử ba mươi giây

# channel nao dang o che do transaction
docker exec -u rabbitmq rmq rabbitmqctl -q list_channels name transactional confirm

Một channel có transactional=true trong đường ống thông lượng cao là chỗ đáng xem lại đầu tiên — trừ khi ai đó cố ý cần txRollback.