InditexTech / InditexTech/scs-outbox

Archiving failure after a successful publish can cause outbox messages to be re-sent and duplicated

Closed
#93 1 comment 0 reactions 1 assignee Claimed by @francisco-bru View on GitHub
kind/bug
Dominant language
Java
Stars
19
Forks
0
Avg merge
9h 31m
Merged PRs (30d)
17

Description

### Detailed description

`OutboxMessagePublisher.publish()` sends the message to the broker, then runs post-send
interceptors (including message archiving), and only then deletes the row from the outbox
store — all inside the same `@Transactional` boundary.

If a post-send interceptor throws after the broker send has already succeeded, the
transaction rolls back and the delete never happens, but the broker publish cannot be
undone.

One concrete way this happens today: when the MongoDB archive module is used with
`json-payload-enabled=true`, and the payload is an Avro record containing a union with a
named type (`record`/`enum`/`fixed`) directly nested in it (e.g. `["null", SomeRecord]`),
`AvroToJsonMapper` (which relies on Avro's native `JsonEncoder`) produces a JSON key equal
to the fully-qualified name of that type, including its namespace (dots). That JSON is
later parsed with `BasicDBObject.parse(...)` in `MongoDbArchivedMessageRepository` and
inserted as a native BSON sub-document with no surrounding try/catch, which can fail.

On top of that, `ParallelPublisher.sortAndPublish()` catches any exception from
`OutboxMessagePublisher.publish()` generically and only logs a warning — it doesn't mark
the message as failed or exclude it from the next scheduler cycle.

Net effect: the message was already delivered to the broker (irreversible), the outbox row
is never deleted, and it gets resent on every following scheduler run — producing
unbounded duplicate publishes for that message.

### Expected behaviour

- A failure in a post-send interceptor (e.g. archiving) should not cause an
already-successfully-published message to be resent.
- Archiving failures should be isolated from the core publish/delete lifecycle and
surfaced distinctly instead of silently looping.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.