Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use writer UUID + sequence number as a request ID for transactional writes #94

Merged
merged 3 commits into from
Nov 12, 2024

Conversation

leviramsey
Copy link
Contributor

We've observed a write of multiple events failing with a software.amazon.awssdk.services.dynamodb.model.TransactionInProgressException: Transaction from previous request is still in progress, indicating that two concurrently executing transactional writes have the same request token.

The plugin has the AWS SDK generate the token (by not specifying one), so the underlying issue seems to be in the SDK. This instead uses the writer UUID (unique per event-sourced actor instantiation) with the sequence number of the first event in the batch appended, to ensure uniqueness (restart of the actor will generate a new UUID, successive persists by the same actor will increment the sequence number component).

Copy link
Member

@octonato octonato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

Copy link
Contributor

@pvlugter pvlugter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼 yes, good catch. Need to include a token for retries.

Comment on lines 127 to 132
val firstEvent = events.head;
val token = s"${firstEvent.writerUuid}-${firstEvent.seqNr.toString}"

val req = TransactWriteItemsRequest
.builder()
.clientRequestToken(token)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like client request token has a max length of 36. So only enough for the UUID in default format.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... then the idempotence would be a problem... so I guess taking the last 36 characters of ${writerUuid}-${seqNr} would work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we encode the UUID more efficiently. Say parse it from string and rewrite as base64. Then it would only take up 22 characters.

bb.asLongBuffer()
.put(uuid.getMostSignificantBits)
.put(uuid.getLeastSignificantBits)
.put(seqNr)
Copy link
Contributor Author

@leviramsey leviramsey Nov 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the maximally compact encoding (e.g. nearly all seqNrs will have quite a few zero high-bytes), but 24 bytes will base64 encode into 32 characters.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼 I think this is good. We get everything needed, and always within the character limit.

bb.asLongBuffer()
.put(uuid.getMostSignificantBits)
.put(uuid.getLeastSignificantBits)
.put(seqNr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼 I think this is good. We get everything needed, and always within the character limit.

@octonato octonato merged commit c33f655 into akka:main Nov 12, 2024
5 checks passed
@patriknw patriknw added this to the 2.0.1 milestone Nov 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants