Ex­act­ly-once de­liv­ery is of­ten claimed to be im­pos­si­ble. It's not - if you read this...

The Prob­lem

There is a lot of con­fu­sion and de­bate about whether or not mes­sag­ing-based dis­trib­uted sys­tems can guar­an­tee ex­act­ly-once de­liv­ery (most of the in­dus­try seems to be stuck at at-least-once de­liv­ery - it suf­fices to google "ex­act­ly-once de­liv­ery" to check for your­self). In this post we will show how it can be done, plus how sim­ple it is with Atomikos. Let's start by defin­ing the rel­e­vant ac­tors on the scene, based on the life­cy­cle of a sin­gle mes­sage (which will be de­liv­ered - as you can prob­a­bly guess - ex­act­ly-once):

The Pro­duc­er

The pro­duc­er is re­spon­si­ble for send­ing a mes­sage as the re­sult of some pri­or pro­cess­ing.

The Bro­ker

The (mes­sage) bro­ker is re­spon­si­ble for keep­ing the mes­sage un­til the rel­e­vant con­sumer picks it up. On pick-up, the mes­sage is "de­liv­ered".

The Con­sumer

The con­sumer is re­spon­si­ble for pick­ing up the mes­sage from the bro­ker and pro­cess­ing it, pre­sum­ably by up­dat­ing its data­base state and/or pub­lish­ing re­lat­ed mes­sages to the bro­ker.

The So­lu­tion

Pro­duc­er Ar­chi­tec­ture

The pro­duc­er should do its pro­cess­ing and send its mes­sage as part of a JTA/XA trans­ac­tion. This en­sures a mes­sage is sent if and only if the trans­ac­tion com­mits. Any fail­ures will re­sult in roll­back of the JTA/XA trans­ac­tion - and no mes­sage will be sent. This means that fail­ures can be safe­ly re­tried un­til they suc­ceed, with­out send­ing the re­sult­ing mes­sage more than once.

xa-producer.png

Bro­ker Ar­chi­tec­ture

As­sum­ing that the bro­ker sup­ports XA, any mes­sage sent to it in a JTA/XA trans­ac­tion will only re­al­ly be sent if the trans­ac­tion com­mits. Like­wise, any mes­sage de­liv­ered to a trans­ac­tion­al con­sumer will only re­al­ly be de­liv­ered (i.e. "re­moved" from the bro­ker an no longer re­de­liv­ered) if the con­sumer's trans­ac­tion com­mits.

Con­sumer Ar­chi­tec­ture

The con­sumer should pick up its mes­sage from the bro­ker as part of a JTA/XA trans­ac­tion. If the con­sumer fails, the trans­ac­tion will roll­back and the mes­sage will be kept in the bro­ker for lat­er (re)de­liv­ery. Note that if this hap­pens then the data­base will roll­back too (as per JTA/XA se­man­tics) so when the mes­sage is re­de­liv­ered the data­base will not have seen it yet. This im­plies that even if (tech­ni­cal­ly speak­ing) the mes­sage is de­liv­ered twice (or more), the ef­fect is the same as if there were ex­act­ly-once de­liv­ery.

xa-consumer.png

Why Does This Work?

The pro­duc­er sends ex­act­ly once - if and when the pro­duc­er trans­ac­tion com­mits. The bro­ker keeps and de­liv­ers the mes­sage un­til the con­sumer trans­ac­tion com­mits. The mes­sage is re­moved from the bro­ker if and only if the con­sumer trans­ac­tion com­mits. Where­as the in­dus­try stan­dard seems to be stuck at "at-least-once", our so­lu­tion en­sures "ex­act­ly-once" de­liv­ery: in no event will the con­sumer's state (data­base or oth­er) process or see the same mes­sage twice. From at-least-once, this brings us to ex­act­ly-once.

Caveat

You may have no­ticed that the con­sumer might con­sis­tent­ly fail to process the mes­sage, for in­stance if the pay­load can­not be parsed due to a bug. In that case, the mes­sage will not be processed, so we don't "even" reach at-least-once se­man­tics. How­ev­er, this also hap­pens in all of the oth­er pro­posed so­lu­tions out there so it would be un­fair to con­sid­er this a weak­ness of our ap­proach. Cor­rect mes­sages should even­tu­al­ly be processed, and ex­act­ly once. Goal achieved!

Ready To Try?

Down­load Trans­ac­tion­sEssen­tials: Our FREE JTA/XA
RSS

Com­ments

Add a com­ment

Cor­po­rate In­for­ma­tion

Atomikos Cor­po­rate Head­quar­ters
Hove­niersstraat, 39/1, 2800
Meche­len, Bel­gium

Con­tact Us

Copy­right 2026 Atomikos BVBA | Our Pri­va­cy Pol­i­cy
By us­ing this site you agree to our cook­ies. More info. That's Fine