Skip to content

Archive & Purge

Event stores grow. Every command that succeeds adds one or more rows. Over months and years, a busy aggregate can accumulate thousands of events, and the global journal can reach millions.

Nagare provides two operations to manage this: archive (soft delete) and purge (hard delete). They serve different purposes and carry different consequences.

Archive

Archiving marks all events for a given aggregate as deleted. The rows stay in the database, but reads skip them.

csharp
var count = await eventStore.Archive(new AggregateId("book-42"));
// count = number of events marked as archived

After archiving, loading the aggregate returns an empty state, as if it never existed. Subscriptions that have already processed the events are unaffected. Subscriptions that start from the beginning will skip archived events.

The events are still physically present. A direct SQL query against the table will find them. This makes archive a safe first step when you're unsure whether you'll need the data later.

When to archive

Archive fits situations where the aggregate's lifecycle has ended but you want a safety net:

  • A user account is deactivated but might be reactivated
  • An order is cancelled and no longer needs to appear in projections
  • A test aggregate should be hidden from production reads

Purge by aggregate

Purging by aggregate hard-deletes every event for that aggregate. The rows are gone. No recovery.

csharp
var count = await eventStore.Purge(new AggregateId("book-42"));
// count = number of events permanently deleted

This is the GDPR right-to-erasure operation. When a user requests deletion of their data, purging their aggregate removes the events from the database entirely.

Irreversible

Purge deletes rows from the database. There is no undo. Make sure you have backups if you need them.

When to purge by aggregate

  • GDPR or CCPA data deletion requests
  • Removing test data from a production database
  • Cleaning up after a failed migration that left orphaned aggregates

Purge by position

Purging by position hard-deletes all events at or before a given global position, across every aggregate.

csharp
var count = await eventStore.Purge(Position.Of(500_000));
// count = number of events permanently deleted

This is the storage reclamation operation. After all subscriptions have processed past a certain position, those events exist only for replay purposes. If you don't need replay (because your projections are healthy and you have backups), you can reclaim the space.

Check your subscriptions first

Before purging by position, verify that every subscription has processed past the target position. Purging events that a subscription hasn't seen yet means those events are lost to that subscription forever.

Finding a safe purge position

Query each subscription's checkpoint to find the lowest position:

sql
SELECT MIN(Position) FROM nagare_checkpoints;

Purge only below that position, with a margin of safety:

csharp
var safePosition = Position.Of(lowestCheckpoint - 1000);
await eventStore.Purge(safePosition);

Archive vs. purge

ArchivePurge (aggregate)Purge (position)
ScopeOne aggregateOne aggregateGlobal, up to position
ReversibleYes (data remains)NoNo
Use caseSoft delete, deactivationGDPR erasureStorage reclamation
Reads afterEmpty aggregateEmpty aggregateUnaffected for later events
SubscriptionsSkip archived eventsEvents goneEvents gone

Combining with snapshots

If you use snapshots, remember that snapshots are stored separately from events. Archiving or purging events does not remove snapshots. Clean up snapshots separately if needed.

Design guidelines

  1. Archive first, purge later. Archive is reversible. Give yourself time to confirm nothing depends on the data before you delete it permanently.

  2. Automate position-based purging. Set up a scheduled job that finds the minimum subscription checkpoint and purges events below it. Don't let the event table grow without bounds.

  3. Log every purge. Record what was purged, when, and why. You can't recover the data, but you can at least explain what happened.

  4. Test purge in staging. Run the same purge logic against a staging environment before touching production. Verify that subscriptions continue working correctly.


Next: Observability

流れ — flow.