NATS JetStream atomic batch publish for event store (NATS 2.12) #23

Open
opened 2026-02-19 22:00:58 +00:00 by ash · 0 comments
Owner

What

NATS 2.12 introduced atomic batch publishing — all-or-nothing multi-message commits. This is critical for event sourcing where a single command can produce multiple events that MUST be persisted atomically.

Why

When Decide() returns 3 events, all 3 must persist or none. Without atomic batch:

  • Partial writes leave the system in broken state
  • Myrra uses PublishMsgAsync which is NOT atomic — individual messages can fail independently

How it works (NATS 2.12)

  • Stream config: AllowAtomicPublish: true
  • Batch headers: NATS-Batch-ID, NATS-Batch-Sequence, NATS-Batch-Commit
  • Messages are staged invisibly on server
  • On commit header: all messages appear atomically to consumers
  • Built-in OCC: NATS-Expected-Last-Sequence (per stream or per subject)
  • Limits: 1000 msgs/batch, 50 in-flight batches, 10s inactivity
  • Single stream constraint (fine for ES)

Ref: https://www.synadia.com/blog/atomic-batch-publishing-nats-2-12
ADR: https://github.com/nats-io/nats-architecture-and-design/blob/main/adr/ADR-50.md

Implementation

NATSEventStore.Append

  • When appending multiple events, use atomic batch headers
  • Single event: normal publish (no batch overhead)
  • Multiple events: batch with commit on last message
  • Set NATS-Expected-Last-Subject-Sequence for OCC on first message

Stream setup

  • Auto-configure AllowAtomicPublish: true on stream creation
  • Detect NATS version and fall back to async publish if < 2.12

CommandBus integration

  • CommandBus handler produces events → batch publish atomically
  • Single writer + atomic batch = bulletproof consistency

Tests

  • Atomic batch: 5 events commit together, consumer sees all or none
  • Partial failure simulation: what happens if commit fails
  • OCC with atomic batch: concurrent writers, correct rejection
  • Performance: batch vs individual publish throughput
  • Fallback: graceful degradation on NATS < 2.12
  • Integration test on VPS (check NATS version first)

Competitive Advantage

Myrra does NOT have this. This makes eskit the first Go ES library with true atomic multi-event commits on NATS.

## What NATS 2.12 introduced atomic batch publishing — all-or-nothing multi-message commits. This is critical for event sourcing where a single command can produce multiple events that MUST be persisted atomically. ## Why When `Decide()` returns 3 events, all 3 must persist or none. Without atomic batch: - Partial writes leave the system in broken state - Myrra uses `PublishMsgAsync` which is NOT atomic — individual messages can fail independently ## How it works (NATS 2.12) - Stream config: `AllowAtomicPublish: true` - Batch headers: `NATS-Batch-ID`, `NATS-Batch-Sequence`, `NATS-Batch-Commit` - Messages are staged invisibly on server - On commit header: all messages appear atomically to consumers - Built-in OCC: `NATS-Expected-Last-Sequence` (per stream or per subject) - Limits: 1000 msgs/batch, 50 in-flight batches, 10s inactivity - Single stream constraint (fine for ES) Ref: https://www.synadia.com/blog/atomic-batch-publishing-nats-2-12 ADR: https://github.com/nats-io/nats-architecture-and-design/blob/main/adr/ADR-50.md ## Implementation ### NATSEventStore.Append - When appending multiple events, use atomic batch headers - Single event: normal publish (no batch overhead) - Multiple events: batch with commit on last message - Set `NATS-Expected-Last-Subject-Sequence` for OCC on first message ### Stream setup - Auto-configure `AllowAtomicPublish: true` on stream creation - Detect NATS version and fall back to async publish if < 2.12 ### CommandBus integration - CommandBus handler produces events → batch publish atomically - Single writer + atomic batch = bulletproof consistency ## Tests - Atomic batch: 5 events commit together, consumer sees all or none - Partial failure simulation: what happens if commit fails - OCC with atomic batch: concurrent writers, correct rejection - Performance: batch vs individual publish throughput - Fallback: graceful degradation on NATS < 2.12 - Integration test on VPS (check NATS version first) ## Competitive Advantage Myrra does NOT have this. This makes eskit the first Go ES library with true atomic multi-event commits on NATS.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ash/eskit#23
No description provided.