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.
var count = await eventStore.Archive(new AggregateId("book-42"));
// count = number of events marked as archivedAfter 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.
var count = await eventStore.Purge(new AggregateId("book-42"));
// count = number of events permanently deletedThis 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.
var count = await eventStore.Purge(Position.Of(500_000));
// count = number of events permanently deletedThis 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:
SELECT MIN(Position) FROM nagare_checkpoints;Purge only below that position, with a margin of safety:
var safePosition = Position.Of(lowestCheckpoint - 1000);
await eventStore.Purge(safePosition);Archive vs. purge
| Archive | Purge (aggregate) | Purge (position) | |
|---|---|---|---|
| Scope | One aggregate | One aggregate | Global, up to position |
| Reversible | Yes (data remains) | No | No |
| Use case | Soft delete, deactivation | GDPR erasure | Storage reclamation |
| Reads after | Empty aggregate | Empty aggregate | Unaffected for later events |
| Subscriptions | Skip archived events | Events gone | Events 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
Archive first, purge later. Archive is reversible. Give yourself time to confirm nothing depends on the data before you delete it permanently.
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.
Log every purge. Record what was purged, when, and why. You can't recover the data, but you can at least explain what happened.
Test purge in staging. Run the same purge logic against a staging environment before touching production. Verify that subscriptions continue working correctly.
Next: Observability