Files

36 lines
2.5 KiB
Plaintext

$ mvn -pl outbox -am test -Dtest=DualWriteProblemTest
(NaiveBillingService: save the Invoice, then separately call kafka.send() -- no
coordinator between the two. spring.kafka.bootstrap-servers points at localhost:19999,
nothing listening there)
2026-10-04T00:52:56.429+05:30 ERROR 5271 --- [outbox] [ main] o.s.k.support.LoggingProducerListener : Exception thrown when sending a message with key='1' and payload='WIDGET-1:1999' to topic invoices-naive:
org.apache.kafka.common.errors.TimeoutException: Topic invoices-naive not present in metadata after 2000 ms.
Exception thrown synchronously from issueInvoiceTheNaiveWay(): org.springframework.kafka.KafkaException: Send failed
Invoice count regardless: 1 (was 0). The database write already committed inside saveInvoiceOnly() before this exception was ever thrown -- the exception came from the Kafka call, several lines later, on an already-committed row.
2026-10-04T00:52:58.495+05:30 ERROR 5271 --- [outbox] [ main] o.s.k.support.LoggingProducerListener : Exception thrown when sending a message with key='2' and payload='WIDGET-1:2999' to topic invoices-naive:
org.apache.kafka.common.errors.TimeoutException: Topic invoices-naive not present in metadata after 2000 ms.
Invoice count after the blocked, failed send: 2 (was 1). The row committed before the send was even attempted.
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
Two real findings here, not one. First, the obvious one: the invoice exists in the
database in both tests, and Kafka never received either message -- that's the dual-write
problem, reproduced with nothing faked beyond pointing Kafka at a port with no listener.
Second, a more specific one that contradicts the usual "fire-and-forget send() fails
silently" framing: when the broker can't be reached to fetch topic metadata at all,
Spring's KafkaTemplate.send() throws synchronously, wrapping the Kafka client's own
TimeoutException in a KafkaException, even though the method's declared return type is
a CompletableFuture. The "silent" version of this bug is real, but it is a narrower
case than total broker unavailability -- it's what happens when the broker is reachable
and the topic's metadata is known, but the specific produce request times out waiting
for an acknowledgment afterward. That failure mode surfaces only through the returned
future or a callback, never as a synchronous exception, and is not reproduced in this
transcript (it needs a broker that accepts the connection and then stops responding
mid-request, not one that was never there to begin with).