Consumer In Action: Delivery, Ack, Nack, Reject, Prefetch, Redelivery
Learn Java RabbitMQ, RabbitMQ Streams, Patterns, and Deployment In Action - Part 006
Consumer-side delivery semantics di Java RabbitMQ: manual ack, nack, reject, redelivery, prefetch, concurrency, poison message handling, dan consumer correctness.
Part 006 — Consumer In Action: Delivery, Ack, Nack, Reject, Prefetch, Redelivery
1. Tujuan Part Ini
Part ini membahas sisi consumer secara production-grade. Fokusnya bukan sekadar “menerima message”, tetapi memastikan consumer:
- tidak ack terlalu awal;
- tidak menciptakan duplicate side effect;
- tidak membuat redelivery storm;
- tidak mengambil message lebih banyak daripada kapasitasnya;
- bisa membedakan transient failure dan poison message;
- punya concurrency model yang aman;
- dapat diamati saat lambat, stuck, gagal, atau overload.
Consumer adalah tempat reliability berubah menjadi business correctness. Producer hanya bisa memastikan message sampai ke broker. Consumer yang menentukan apakah message diproses dengan benar.
2. Mental Model: Delivery is a Lease, Not Ownership
Saat RabbitMQ mengirim message ke consumer dengan manual acknowledgement, message belum selesai. Message sedang “dipinjamkan” kepada consumer.
Key invariant:
Ack hanya boleh dikirim setelah side effect yang ingin dianggap final sudah aman.
Jika ack dikirim sebelum DB commit, message bisa hilang secara bisnis.
3. Consumer API Basics
Contoh raw Java client:
boolean autoAck = false;
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
long deliveryTag = delivery.getEnvelope().getDeliveryTag();
try {
process(delivery);
channel.basicAck(deliveryTag, false);
} catch (RetryableException e) {
channel.basicNack(deliveryTag, false, true);
} catch (PoisonMessageException e) {
channel.basicNack(deliveryTag, false, false);
}
};
CancelCallback cancelCallback = consumerTag -> {
log.warn("consumer cancelled consumerTag={}", consumerTag);
};
channel.basicConsume("order-created.billing.queue", autoAck, deliverCallback, cancelCallback);
Parameter utama:
| Concept | Meaning |
|---|---|
autoAck=false | Consumer wajib ack/nack/reject manual |
deliveryTag | Identifier delivery pada channel tersebut |
basicAck | Processing sukses; broker boleh menghapus delivery |
basicNack | Processing gagal; bisa requeue atau dead-letter/discard |
basicReject | Reject satu message; lebih terbatas daripada nack |
CancelCallback | Broker membatalkan consumer, misalnya queue deleted |
4. Delivery Tag Scope
deliveryTag scoped ke channel, bukan global.
Artinya:
Channel A deliveryTag=42 != Channel B deliveryTag=42
Jangan ack delivery dari channel lain.
Buruk:
// delivery diterima di channelA, tapi ack memakai channelB
channelB.basicAck(deliveryTag, false);
Hasilnya bisa channel exception karena delivery tag tidak dikenal pada channel tersebut.
Rule:
Satu consumer execution path harus tahu channel owner-nya. Jangan lempar deliveryTag ke thread lain tanpa mekanisme ack yang aman pada channel yang sama.
5. Auto Ack vs Manual Ack
5.1 Auto Ack
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
Dengan auto ack, RabbitMQ menganggap message selesai begitu dikirim ke consumer.
Risiko:
- consumer crash setelah menerima tetapi sebelum proses;
- exception saat deserialize;
- DB failure;
- process killed;
- executor queue penuh;
- external API timeout.
Message sudah dianggap selesai, sehingga tidak redeliver.
Auto ack cocok untuk:
- non-critical telemetry;
- best-effort metrics;
- local demo;
- workload yang memang boleh hilang.
5.2 Manual Ack
channel.basicConsume(queueName, false, deliverCallback, cancelCallback);
Manual ack cocok untuk business-critical processing.
Receive -> Validate -> Process -> Commit -> Ack
Bukan:
Receive -> Ack -> Process -> Hope
6. Ack Timing and Transaction Boundary
Consumer harus menempatkan ack setelah durable side effect.
6.1 Correct Pattern
try {
BusinessMessage message = deserialize(delivery.getBody());
transactionTemplate.executeWithoutResult(tx -> {
dedupRepository.insertIfAbsent(message.messageId());
businessHandler.handle(message);
});
channel.basicAck(deliveryTag, false);
} catch (DuplicateMessageException duplicate) {
channel.basicAck(deliveryTag, false);
} catch (RetryableException retryable) {
channel.basicNack(deliveryTag, false, true);
} catch (Exception fatal) {
channel.basicNack(deliveryTag, false, false);
}
6.2 Dangerous Pattern
channel.basicAck(deliveryTag, false);
businessHandler.handle(message);
Jika process crash setelah ack, message hilang secara permanen dari queue, tetapi business side effect belum terjadi.
7. basicAck
channel.basicAck(deliveryTag, false);
Parameter kedua multiple menentukan apakah ack berlaku untuk satu delivery atau semua delivery sampai tag tersebut pada channel yang sama.
| Call | Meaning |
|---|---|
basicAck(tag, false) | Ack satu delivery |
basicAck(tag, true) | Ack semua unacked delivery sampai tag tersebut |
multiple=true bisa efisien untuk batch sequential processing, tetapi berbahaya jika message diproses paralel.
7.1 Safe Single Ack
Gunakan ini untuk kebanyakan consumer:
channel.basicAck(deliveryTag, false);
7.2 Batch Ack
Batch ack aman jika:
- processing sequential;
- semua message sebelum tag tersebut sukses;
- tidak ada parallel out-of-order completion;
- channel hanya dipakai oleh flow tersebut.
long lastDeliveryTag = 0;
int processed = 0;
for (Delivery delivery : batch) {
process(delivery);
lastDeliveryTag = delivery.getEnvelope().getDeliveryTag();
processed++;
if (processed % 100 == 0) {
channel.basicAck(lastDeliveryTag, true);
}
}
Jika parallel, jangan gunakan multiple=true kecuali punya ack coordinator yang memahami gap.
8. basicNack and basicReject
8.1 basicNack
channel.basicNack(deliveryTag, false, true); // requeue
channel.basicNack(deliveryTag, false, false); // dead-letter or discard
basicNack mendukung multiple, sehingga bisa menolak banyak delivery sekaligus.
8.2 basicReject
channel.basicReject(deliveryTag, false);
basicReject menolak satu delivery. Tidak ada multiple.
8.3 Requeue Flag
| Call | Result |
|---|---|
basicNack(tag, false, true) | Message dikembalikan ke queue untuk redelivery |
basicNack(tag, false, false) | Message dead-letter jika DLX configured, atau discard |
basicReject(tag, false) | Sama secara practical untuk satu message: dead-letter/discard |
basicReject(tag, true) | Requeue satu message |
Rule:
Jangan requeue message yang sudah diketahui poison. Requeue poison message akan menciptakan loop.
9. Redelivery Semantics
RabbitMQ bisa mengirim ulang message jika:
- consumer connection/channel close sebelum ack;
- consumer mengirim nack/reject dengan requeue true;
- broker recovery/failover membuat unacked delivery dikembalikan;
- consumer timeout/policy tertentu membuat delivery dianggap tidak selesai.
Delivery memiliki flag:
boolean redelivered = delivery.getEnvelope().isRedeliver();
Flag ini berarti message pernah dikirim sebelumnya, tetapi bukan bukti jumlah retry akurat.
9.1 Redelivered Is a Weak Signal
redelivered=true tidak menjawab:
- sudah berapa kali dicoba;
- consumer mana yang gagal;
- apakah side effect sebelumnya terjadi;
- apakah message poison;
- apakah retry masih layak.
Untuk retry count, gunakan header atau DLQ topology, bukan hanya redelivery flag.
10. Poison Message
Poison message adalah message yang tidak akan sukses walau dicoba ulang dalam kondisi saat ini.
Contoh:
- schema tidak kompatibel;
- required field hilang;
- business invariant invalid;
- tenant tidak dikenal;
- entity referentially impossible;
- payload corrupt;
- message version unsupported;
- command sudah kadaluarsa;
- bug deterministic pada handler.
Poison message harus keluar dari hot path.
11. Retry Decision in Consumer
Consumer failure harus diklasifikasikan.
| Failure Type | Example | Action |
|---|---|---|
| Transient infrastructure | DB timeout, HTTP 503, temporary network issue | Retry with backoff |
| Resource pressure | Thread pool full, downstream slow | Stop consuming / nack with delay strategy |
| Data contract error | Invalid JSON, unknown schema version | DLQ/parking lot |
| Business terminal | Order already cancelled, impossible transition | Ack with business no-op or DLQ depending audit need |
| Duplicate | Message id already processed | Ack |
| Unknown bug | Null pointer, unexpected exception | Limited retry, then DLQ |
Do not treat all exceptions equally.
Bad:
catch (Exception e) {
channel.basicNack(deliveryTag, false, true);
}
Ini bisa menyebabkan infinite immediate retry.
Better:
catch (InvalidMessageException e) {
channel.basicNack(deliveryTag, false, false);
} catch (DuplicateMessageException e) {
channel.basicAck(deliveryTag, false);
} catch (RetryableInfrastructureException e) {
retryPublisher.schedule(delivery, e);
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
failureClassifier.handleUnknown(delivery, e);
}
Catatan: retry delayed biasanya lebih baik dilakukan lewat retry topology, bukan immediate requeue=true.
12. Prefetch: Consumer Credit and Backpressure
Prefetch membatasi jumlah unacked delivery yang boleh dikirim broker ke consumer.
int prefetchCount = 50;
channel.basicQos(prefetchCount);
Mental model:
prefetch = max unacked messages allowed on this consumer/channel scope
Jika prefetch terlalu tinggi:
- consumer memory naik;
- message terdistribusi terlalu jauh;
- slow consumer memegang banyak message;
- redelivery burst besar saat consumer crash;
- latency tail memburuk;
- fairness antar consumer menurun.
Jika terlalu rendah:
- throughput bisa kurang;
- network round trip lebih dominan;
- worker idle jika processing cepat.
13. Prefetch Sizing
Baseline:
prefetch ~= concurrency * small multiplier
Contoh:
| Consumer Model | Suggested Starting Prefetch |
|---|---|
| Single-thread sequential | 1–10 |
| 8 worker threads, CPU-bound | 8–16 |
| 16 worker threads, IO-bound | 32–128 |
| Batch DB writer | batch size × 1–2 |
| Strict ordering per queue | 1 |
| Expensive large payload | low, maybe 1–5 |
Formula praktis:
prefetch = workerConcurrency + bufferAllowance
Jangan mulai dari 1000. Mulai konservatif, ukur throughput dan latency, lalu naikkan.
13.1 Prefetch and Crash Blast Radius
Jika prefetch 1000 dan consumer crash, sampai 1000 unacked message bisa redeliver. Ini bisa membuat duplicate storm.
blast radius = prefetch × number of crashed consumers
Jika ada 20 consumer dengan prefetch 500:
potential redelivery burst = 10,000 messages
14. Consumer Concurrency Model
Ada beberapa model.
14.1 One Channel Per Consumer Thread
Paling sederhana dan aman.
thread-1 -> channel-1 -> consumer-1
thread-2 -> channel-2 -> consumer-2
thread-3 -> channel-3 -> consumer-3
Kelebihan:
- deliveryTag scope jelas;
- ack dilakukan pada channel yang sama;
- failure isolation lebih baik;
- reasoning lebih mudah.
Kekurangan:
- lebih banyak channel;
- perlu lifecycle management.
14.2 One Consumer Dispatches to Worker Pool
Perlu hati-hati:
- executor queue harus bounded;
- jangan ack dengan
multiple=truejika completion out-of-order; - channel access harus thread-safe secara desain aplikasi;
- backpressure harus terjadi jika executor penuh;
- shutdown harus menunggu worker selesai atau nack ulang.
14.3 Recommended Starting Point
Untuk service bisnis biasa:
N consumer instances × M channels/consumers × prefetch K
Misal:
4 pods × 4 consumers each × prefetch 20 = max 320 unacked messages
Ini lebih mudah dikontrol daripada satu consumer dengan executor queue besar dan prefetch 5000.
15. Bounded Executor Pattern
Jika perlu worker pool, jangan gunakan unbounded queue.
ThreadPoolExecutor executor = new ThreadPoolExecutor(
8,
8,
0L,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.AbortPolicy()
);
Deliver callback:
DeliverCallback callback = (consumerTag, delivery) -> {
try {
executor.execute(() -> handleDelivery(channel, delivery));
} catch (RejectedExecutionException full) {
channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, true);
}
};
Tetapi requeue langsung saat executor penuh bisa menciptakan tight loop. Lebih baik:
- set prefetch sesuai kapasitas executor;
- pause/cancel consumer saat overload;
- gunakan delayed retry path;
- scale consumer jika bottleneck memang consumer capacity;
- jangan menerima delivery lebih banyak daripada worker capacity.
16. Ack Coordinator for Parallel Processing
Jika parallel processing tetap ingin batch ack dengan multiple=true, butuh gap tracking.
Contoh delivery tags selesai:
1 success
2 success
3 still running
4 success
5 success
Tidak boleh basicAck(5, true) karena tag 3 belum selesai.
Ack coordinator hanya boleh ack contiguous success range:
ack up to 2
wait for 3
then ack up to 5
Simpler production rule:
Untuk parallel consumer, gunakan
basicAck(tag, false)per delivery kecuali benar-benar butuh optimization dan punya coordinator yang benar.
17. Consumer Cancellation
Consumer bisa dibatalkan broker, misalnya:
- queue dihapus;
- permission berubah;
- exclusive consumer conflict;
- node failover/rebalance;
- admin action.
Tangani CancelCallback:
CancelCallback cancelCallback = consumerTag -> {
log.warn("rabbitmq.consumer.cancelled queue={} consumerTag={}", queueName, consumerTag);
consumerCancelledCounter.increment();
readiness.markNotReady("consumer cancelled: " + consumerTag);
};
Jangan diam-diam mengabaikan cancellation. Service bisa terlihat hidup tetapi tidak memproses apa pun.
18. Shutdown Semantics
Shutdown consumer harus graceful.
Urutan recommended:
- mark readiness false;
- stop accepting new deliveries atau cancel consumer;
- wait in-flight processing selesai sampai timeout;
- ack yang sudah sukses;
- nack/requeue yang belum diproses jika perlu;
- close channel;
- close connection.
Dalam Kubernetes, pastikan terminationGracePeriodSeconds cukup untuk processing max time atau consumer punya checkpoint/idempotency.
19. Deserialization Boundary
Deserialize adalah bagian dari processing, bukan preprocessing yang boleh gagal tanpa ack policy.
try {
BusinessMessage message = serializer.deserialize(delivery.getBody());
handler.handle(message);
channel.basicAck(deliveryTag, false);
} catch (JsonProcessingException invalidPayload) {
channel.basicNack(deliveryTag, false, false);
}
Jika payload invalid dan kita requeue=true, consumer akan membaca message yang sama terus-menerus.
Tambahkan metadata ke DLQ:
- original exchange;
- original routing key;
- exception class;
- exception message sanitized;
- consumer service;
- failure timestamp;
- schema version;
- message id.
20. Idempotent Consumer Baseline
Karena RabbitMQ memberi at-least-once semantics jika kita memakai manual ack dan retry, consumer harus idempotent.
Minimal pattern:
@Transactional
public void handle(BusinessMessage message) {
boolean firstTime = processedMessageRepository.insertIfAbsent(
message.messageId(),
message.consumerName()
);
if (!firstTime) {
return;
}
businessOperation.apply(message);
}
Jika duplicate datang:
try {
handler.handle(message);
channel.basicAck(deliveryTag, false);
} catch (DuplicateMessageException duplicate) {
channel.basicAck(deliveryTag, false);
}
Duplicate adalah sukses dari sudut pandang queue. Jangan nack duplicate.
21. Ordering and Ack Interaction
Jika queue punya multiple consumers, global order tidak bisa diasumsikan.
Bahkan satu consumer bisa mengubah order bila:
- prefetch > 1;
- processing parallel;
- nack/requeue;
- retry delayed;
- consumer crash;
- multiple queues/partitions.
Jika ordering penting:
| Requirement | Design |
|---|---|
| Per-entity order | Partition by entity key ke queue/stream partition |
| Strict queue order | Single consumer, prefetch 1, no parallel processing |
| Replayable order | RabbitMQ Stream/Super Stream |
| Causal consistency | Causation id + version check + idempotent state transition |
Jangan mengandalkan default queue behavior untuk workflow yang butuh ordering kuat tanpa mengontrol prefetch dan concurrency.
22. Metrics for Consumer
Minimal metrics:
| Metric | Type | Meaning |
|---|---|---|
rabbitmq_consumer_delivery_total | counter | Messages delivered to consumer |
rabbitmq_consumer_ack_total | counter | Successful ack count |
rabbitmq_consumer_nack_total | counter | Nack count |
rabbitmq_consumer_reject_total | counter | Reject count |
rabbitmq_consumer_redelivery_total | counter | Redelivered messages observed |
rabbitmq_consumer_processing_seconds | histogram | Handler latency |
rabbitmq_consumer_deserialization_failure_total | counter | Invalid payload/schema |
rabbitmq_consumer_in_flight | gauge | Current unacked/in-process count |
rabbitmq_consumer_executor_queue_depth | gauge | Local worker backlog |
rabbitmq_consumer_duplicate_total | counter | Idempotency duplicate hit |
rabbitmq_consumer_poison_total | counter | Terminal failures |
Queue-side metrics:
- ready messages;
- unacked messages;
- publish rate;
- deliver/get rate;
- ack rate;
- redeliver rate;
- consumer count;
- consumer utilisation;
- memory/disk state;
- quorum/leader health where applicable.
23. Structured Logging
Log processing outcome, not every payload.
{
"event": "rabbitmq.consumer.processed",
"queue": "order-created.billing.queue",
"messageId": "01J...",
"correlationId": "checkout-...",
"consumer": "billing-service",
"messageType": "order.created.v1",
"redelivered": false,
"processingMs": 37,
"outcome": "ack"
}
For failure:
{
"event": "rabbitmq.consumer.failed",
"queue": "order-created.billing.queue",
"messageId": "01J...",
"correlationId": "checkout-...",
"outcome": "nack_no_requeue",
"failureClass": "InvalidSchemaVersionException",
"retryable": false
}
Never log sensitive message body by default.
24. Consumer Health and Readiness
Consumer readiness should answer:
Can this service safely accept more deliveries right now?
Signals:
- connection open;
- channel open;
- consumer registered;
- not cancelled;
- executor not saturated;
- downstream dependencies healthy enough;
- processing latency under threshold;
- local in-flight below threshold;
- DLQ/poison rate not exploding;
- schema version supported.
Bad readiness:
JVM process alive => ready
Better readiness:
Consumer can accept, process, and ack within configured safety limits.
25. Testing Consumer Semantics
25.1 Ack After Commit Test
Scenario:
- deliver message;
- handler writes DB;
- crash before ack;
- message redelivers;
- idempotency prevents duplicate side effect;
- second attempt acks.
Expected:
business effect once, message ack eventually
25.2 Ack Before Commit Failure Test
Intentionally write bad implementation:
ack();
crash();
commit never happens;
Expected:
message loss visible in test
This test teaches why ack timing matters.
25.3 Poison Message Test
- publish invalid schema;
- consumer receives;
- deserialization fails;
- message dead-lettered/parked;
- no infinite redelivery.
Expected:
poison message leaves hot path quickly
25.4 Prefetch Blast Radius Test
- set prefetch 1000;
- consume slow;
- kill consumer;
- observe redelivery burst;
- repeat with prefetch 20.
Expected:
prefetch controls crash blast radius
26. Consumer Failure Matrix
| Step | Failure | Message State | Correct Design |
|---|---|---|---|
| Before delivery | Broker unavailable | Still in queue | Consumer reconnects |
| After delivery before processing | Consumer crash | Unacked redelivered | Idempotent handler |
| During deserialize | Invalid payload | Unacked | Nack no requeue/DLQ |
| After DB commit before ack | Consumer crash | Redelivered | Dedup then ack |
| After ack before DB commit | Consumer crash | Lost business effect | Avoid this design |
| During external API call | Timeout | Unclear external effect | Idempotent external call / retry policy |
| Executor full | Cannot process | Delivery should not accumulate | Prefetch + bounded executor + pause |
| Poison message requeued | Immediate redelivery loop | Hot path blocked | DLQ/parking lot |
27. Common Consumer Anti-Patterns
| Anti-Pattern | Why It Fails |
|---|---|
autoAck=true for business message | Message loss on crash/error |
| Ack before DB commit | Business side effect can disappear |
| Catch all exception then requeue | Infinite redelivery loop |
| No idempotency table/key | Duplicate creates duplicate side effect |
| Prefetch too high | Memory pressure and redelivery burst |
| Unbounded worker queue | Local backlog invisible to broker |
Parallel processing with basicAck(tag, true) | Can ack unfinished messages |
Ignoring CancelCallback | Service appears alive but stopped consuming |
| Logging full payload | Security and compliance risk |
| Treating redelivered flag as retry count | Misclassification of failures |
28. Production Consumer Blueprint
Modules:
DeliveryEnvelopeReaderMessageDeserializerMessageContractValidatorIdempotencyServiceBusinessMessageHandlerFailureClassifierAckStrategyRetryPublisherConsumerMetricsConsumerReadiness
29. Minimal Consumer Wrapper
public final class ReliableConsumer {
private final Channel channel;
private final MessageDeserializer deserializer;
private final BusinessHandler handler;
private final FailureClassifier failureClassifier;
public void start(String queue) throws IOException {
channel.basicQos(50);
channel.basicConsume(queue, false, this::onDelivery, this::onCancel);
}
private void onDelivery(String consumerTag, Delivery delivery) throws IOException {
long tag = delivery.getEnvelope().getDeliveryTag();
try {
BusinessMessage message = deserializer.deserialize(delivery);
handler.handle(message);
channel.basicAck(tag, false);
} catch (DuplicateMessageException duplicate) {
channel.basicAck(tag, false);
} catch (Exception e) {
FailureDecision decision = failureClassifier.classify(e, delivery);
switch (decision.action()) {
case ACK_AS_NOOP -> channel.basicAck(tag, false);
case REQUEUE -> channel.basicNack(tag, false, true);
case DEAD_LETTER -> channel.basicNack(tag, false, false);
case RETRY_VIA_DELAY_QUEUE -> {
decision.retryPublisher().publishRetry(delivery);
channel.basicAck(tag, false);
}
}
}
}
private void onCancel(String consumerTag) {
log.warn("consumer cancelled: {}", consumerTag);
}
}
Dalam sistem besar, REQUEUE langsung sebaiknya jarang dipakai. Delayed retry topology lebih aman untuk menghindari hot loop.
30. Practice Lab
Lab 1 — Ack Timing
- Consumer menerima message.
- Simulasikan DB commit sukses, lalu crash sebelum ack.
- Restart consumer.
- Pastikan message redeliver.
- Dedup harus mencegah double insert.
Lab 2 — Poison Handling
- Publish message dengan schema invalid.
- Consumer harus nack no requeue.
- Message masuk DLQ.
- Redelivery rate tidak naik terus.
Lab 3 — Prefetch Tuning
- Jalankan 1 consumer dengan prefetch 1, 10, 100, 1000.
- Ukur throughput, p95 latency, memory, unacked count.
- Kill consumer saat load.
- Catat redelivery burst.
Lab 4 — Parallel Ack Bug
- Prefetch 10.
- Process message paralel.
- Buat tag 3 lambat, tag 4–10 sukses.
- Coba
basicAck(10, true). - Buktikan tag 3 bisa ter-ack sebelum selesai.
Expected learning:
Ack batching tanpa ordering control adalah correctness bug.
31. Self-Correction Checklist
Sebelum lanjut ke Spring AMQP, jawab ini:
- Mengapa delivery dengan manual ack lebih mirip lease daripada ownership?
- Apa risiko
autoAck=true? - Kapan ack boleh dikirim?
- Apa yang terjadi jika consumer crash setelah DB commit tetapi sebelum ack?
- Mengapa duplicate harus di-ack, bukan di-nack?
- Apa beda
basicNackdanbasicReject? - Kapan
requeue=trueberbahaya? - Mengapa redelivered flag bukan retry count?
- Bagaimana prefetch memengaruhi throughput dan blast radius?
- Mengapa
basicAck(tag, true)berbahaya pada parallel processing?
Jika belum bisa menjawab, ulangi bagian 5–16.
32. Ringkasan
Consumer RabbitMQ yang benar bukan hanya menerima message. Ia mengelola contract:
- delivery diterima tetapi belum selesai;
- processing dilakukan dengan validation dan idempotency;
- side effect dibuat aman sebelum ack;
- duplicate diperlakukan sebagai sukses/no-op;
- transient failure diarahkan ke retry dengan backoff;
- poison message keluar dari hot path;
- prefetch dan concurrency dibatasi agar consumer tidak menjadi sumber overload;
- semua outcome terukur lewat metrics, logs, traces, dan DLQ visibility.
Golden rule:
Ack adalah komitmen. Jangan ack sebelum sistem benar-benar siap kehilangan kesempatan memproses message itu lagi.
Part berikutnya akan masuk ke Spring AMQP: bagaimana memakai RabbitTemplate, listener container, converter, retry, concurrency, dan publisher confirms tanpa kehilangan pemahaman low-level yang sudah dibangun.
33. Referensi Resmi
- RabbitMQ Consumer Acknowledgements and Publisher Confirms: https://www.rabbitmq.com/docs/confirms
- RabbitMQ Negative Acknowledgements: https://www.rabbitmq.com/docs/nack
- RabbitMQ Consumer Prefetch: https://www.rabbitmq.com/docs/consumer-prefetch
- RabbitMQ Java Client API Guide: https://www.rabbitmq.com/client-libraries/java-api-guide
- RabbitMQ Work Queues Java Tutorial: https://www.rabbitmq.com/tutorials/tutorial-two-java
- RabbitMQ Reliability Guide: https://www.rabbitmq.com/docs/reliability
You just completed lesson 06 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.