Save first, then publish: a simple rule for not losing work
One of the most common hidden bugs in event-driven systems is publishing the news before the fact is recorded. The right order — save first, then publish — removes that bug at the root.
The Felesh team5 min
In event-driven systems, a subtle bug often lies in wait: publishing the news before the fact is recorded. The bug is silent. It rarely appears in testing, revealing itself only in production, under load. A strict ordering rule resolves it entirely: save first, then publish.
The same code passes on an idle database and fails on a busy one: only the commit time moved, so a test never sees the race.
The shape of the problem
When a component finishes work, it must perform two actions: write the result to durable memory, and publish a completion event. If you invert this sequence — emitting the event before the write commits — a race condition opens. A downstream component hears the event, reacts immediately, and attempts to read the result. Because the save has not yet completed, the consumer reads stale or empty data. The failure cascades from there.
Published before the save, the event reaches the consumer at 3 ms; its read lands inside the 12 ms write and finds nothing.
The right order
The fix is strict linear execution: write the result to durable memory, ensure the commit has completed, and only then publish the event. Any component receiving the event is now guaranteed to find the corresponding data in memory. This enforces a core principle of event-driven architecture: the event is merely a trigger, and the truth is always in memory. If the truth is not yet written, the trigger must not fire.
Write, wait for the commit, then publish: by the time the consumer hears of A-7031, the row is already there to read.
Why this rule matters
This ordering aligns with another architectural constraint: the event should carry no data, only an identifier. Including state in the event creates the temptation to emit it before persisting the data — which introduces the exact race condition. When the event carries only a reference, forcing consumers to query the data store for the truth, the “save first, then publish” pattern transitions from a best practice to a logical necessity.
An event that carries state invites sending it before the save; an event that carries only an id makes every consumer read the stored truth.
At-least-once, not at-most-once
This approach carries a consequence. In durable systems, event delivery is almost always “at least once,” meaning an event may be delivered twice. Consumers must therefore be idempotent — processing the identical event twice must not produce an incorrect result. Combining strict publish-after-save ordering with idempotent consumers yields a system that neither drops messages nor corrupts state upon retry.
A retried delivery charges 640 twice for a consumer that just acts, and is skipped by one that checks the id first.
Putting it together
The rule appears minor, but it forms the boundary between a system that remains resilient under load and one that fails irreproducibly. Enforce the order of operations — record the fact first, then announce the change — and an entire class of race conditions is eliminated. In designing durable systems, deterministic operational orderings matter most.