Investigate: StreamVersion vs EventId for optimistic concurrency #249
Labels
No labels
adr
automated
bug
chore
dependencies
documentation
enhancement
epic
github-actions
P1-high
P2-medium
P3-low
release
research
rust
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jwilger/eventcore#249
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Overview
Investigate whether using EventId (UUID) for optimistic concurrency instead of StreamVersion (sequential integer) would provide benefits.
Current Approach
Alternative Approach
Questions to Answer
Acceptance Criteria
Migrated from beads issue: eventcore-aet
Research: StreamVersion vs EventId for Optimistic Concurrency
Current Approach: StreamVersion (Sequential Integer per Stream)
EventCore uses per-stream monotonic version numbers (0 = empty, incrementing by 1) for optimistic concurrency control, as defined in ADR-007.
How it works:
ConcurrencyErrortriggers automatic retryAll three backends (Postgres, SQLite, in-memory) implement atomic version verification within their transaction/lock boundaries.
Alternative: EventId (UUID) for Concurrency
This would replace "expected version N" with "last event I saw was UUID X" — the store would reject writes if a stream's latest event ID doesn't match.
Tradeoff Analysis
Key Findings
EventId already exists in the codebase — events are identified by UUID v7 for global ordering. But this serves a different purpose (cross-stream pagination for projections via
StreamPosition), not per-stream conflict detection.The two mechanisms are complementary, not competing: StreamVersion handles per-stream optimistic concurrency (write-side). EventId handles cross-stream position tracking (read-side/projections). Replacing one with the other would conflate two distinct concerns.
UUID-based concurrency would complicate every backend: Currently, Postgres uses a trigger comparing integers, SQLite queries
MAX(stream_version), and in-memory compares a stored integer. Switching to EventId would require each backend to look up the latest event UUID per stream — strictly more work for no correctness gain.No industry precedent: EventStoreDB (the reference implementation for event sourcing) uses sequential stream versions. Marten (PostgreSQL event store) uses sequential versions. Axon Framework uses sequence numbers. No mainstream event sourcing library uses event IDs for optimistic concurrency.
Sequential versions enable gap-free invariants: "Version N implies events 1..N exist" is a powerful guarantee that simplifies reasoning about stream state. UUIDs cannot provide this.
Recommendation
Keep the current StreamVersion approach. It is:
There is no benefit to switching. The perceived "coordination" advantage of UUIDs is irrelevant here — StreamVersion requires no coordination either, since it's derived from the stream's own event count within an atomic transaction.