Safety

aether-persistence

WAL-backed durable state with PostgreSQL, Redis, and NATS backends

The aether-persistence crate provides durable state management for Aether worlds, including a write-ahead log, ephemeral checkpointing, snapshot policies, and trait-based abstractions for PostgreSQL, Redis, and NATS backends.

Overview

World state must survive server restarts and handle failover gracefully. This crate provides a policy-driven persistence layer:

  • Write-ahead log (WAL) that ensures durability by recording state mutations before applying them.
  • Snapshot system with configurable intervals for ephemeral and durable checkpoints.
  • Critical state tracking for high-priority mutations that require immediate durability.
  • PostgreSQL backend for durable relational storage.
  • Redis backend for fast ephemeral caching.
  • NATS JetStream for event bus with durable subscriptions.
  • Connection pooling with environment-variable-driven configuration.
  • Database migrations framework for schema evolution.

Key Types

PersistenceRuntime

The top-level runtime that coordinates WAL writes, snapshots, and backend interactions.

use aether_persistence::{PersistenceRuntime, PersistenceRuntimeConfig};

let config = PersistenceRuntimeConfig {
    wal_flush_interval_ms: 100,
    snapshot_interval_seconds: 300,
    backend: "postgres".into(),
};
let runtime = PersistenceRuntime::new(config);

WalWriteCoordinator

Manages write-ahead log entries, ensuring mutations are recorded before being applied.

use aether_persistence::{WalWriteCoordinator, WalEntry, WalDurability, WalAppendResult};

let mut wal = WalWriteCoordinator::new();
let result: WalAppendResult = wal.append(WalEntry {
    sequence: next_seq,
    data: mutation_bytes,
    durability: WalDurability::Fsync,
})?;

SnapshotRecorder

Captures periodic snapshots of world state for fast recovery.

use aether_persistence::{SnapshotRecorder, SnapshotPolicy, SnapshotKind, Snapshot};

let recorder = SnapshotRecorder::new(SnapshotPolicy {
    interval_seconds: 300,
    kind: SnapshotKind::Full,
    max_retained: 5,
});
let snapshot: Snapshot = recorder.capture(&world_state)?;

WorldPersistenceProfile

Configures the persistence behavior for a specific world.

use aether_persistence::{WorldPersistenceProfile, WorldPersistenceClass};

let profile = WorldPersistenceProfile {
    class: WorldPersistenceClass::Durable,
    wal_enabled: true,
    snapshot_interval_seconds: 300,
};

DatabaseClient

PostgreSQL client for durable state operations.

use aether_persistence::{DatabaseClient, ConnectionConfig};

let config = ConnectionConfig::from_env()?;
let client = DatabaseClient::new(config).await?;

CacheClient

Redis client for ephemeral state caching.

use aether_persistence::{CacheClient, ConnectionConfig};

let config = ConnectionConfig::from_env()?;
let cache = CacheClient::new(config).await?;

EventBus

NATS JetStream event bus for durable event distribution.

use aether_persistence::{EventBus, EventMessage, EventSubscription};

let bus = EventBus::new(nats_config).await?;
bus.publish("world.events", EventMessage {
    topic: "entity.spawned".into(),
    payload: event_bytes,
}).await?;

Migration

Database migration framework for schema evolution.

use aether_persistence::Migration;

let migration = Migration {
    version: 3,
    name: "add_world_metadata".into(),
    up_sql: "ALTER TABLE worlds ADD COLUMN metadata JSONB;".into(),
    down_sql: "ALTER TABLE worlds DROP COLUMN metadata;".into(),
};

Usage Examples

World Recovery

use aether_persistence::{
    WorldRecovery, WalWriteCoordinator, SnapshotRecorder, Snapshot,
};

// On server restart, replay from latest snapshot + WAL entries
let snapshot = SnapshotRecorder::load_latest()?;
let wal_entries = WalWriteCoordinator::replay_after(snapshot.sequence)?;
let recovered_state = WorldRecovery::restore(snapshot, wal_entries)?;

Critical State Write

use aether_persistence::{
    SyncStateMutation, CriticalStateKey, CriticalStatePriority,
};

let mutation = SyncStateMutation {
    key: CriticalStateKey::new("player_inventory", player_id),
    priority: CriticalStatePriority::High,
    data: inventory_bytes,
};