State Persistence¶
By default the simulator is purely in-memory: clusters exist only for the lifetime of the server process. SQLite persistence makes cluster configuration and event history survive server restarts.
Enabling persistence¶
Set sqlite_path in config.yaml:
Or use the environment variable:
The parent directory (./data/) is created automatically with 0750 permissions if it
doesn't exist.
To disable persistence, leave sqlite_path empty or unset.
What is persisted¶
| Table | What |
|---|---|
clusters |
Cluster ID, strategy config, node IDs, current leader, creation timestamp |
cluster_history |
Per-cluster event log: sequence number, event JSON, optional state snapshot JSON |
The cluster_history table has an ON DELETE CASCADE foreign key to clusters, so
deleting a cluster via the API also removes all its stored history.
Restore on startup¶
When the server starts with a configured sqlite_path, it:
- Opens the SQLite database (creates it with WAL mode if new).
- Loads all cluster records.
- For each cluster, loads the stored event history (up to
historyMaxSizeentries). - Pre-populates the in-memory ring buffer with the stored history so event sequence numbers continue from where they left off — not from zero.
- Recreates the cluster in the orchestrator and starts all nodes.
This means a restart is transparent: the UI reconnects, the cluster is alive, and the timeline scrubber shows the full history including events from before the restart.
SQLite configuration¶
The driver is modernc.org/sqlite — a pure-Go CGo-free port. The DSN pragmas applied
at open time:
| Pragma | Value | Purpose |
|---|---|---|
journal_mode |
WAL |
Write-Ahead Logging for better concurrent read performance |
foreign_keys |
ON |
Enforce the cluster_history → clusters cascade |
busy_timeout |
5000 |
5-second retry window on write contention |
The connection pool is limited to 1 writer (db.SetMaxOpenConns(1)) because WAL
mode handles concurrent reads but still requires a single writer.
Ephemeral mode (tests and CI)¶
Use :memory: to get a fully functional but non-persistent SQLite database:
This is what the E2E test suite uses. The database lives only for the duration of the process.
Disabling persistence¶
Set sqlite_path to an empty string or leave it out of config.yaml:
The orchestrator runs in pure in-memory mode — no SQLite dependency at all.