The Store

For an application that keeps its graph, the Store does everything of the previous pages for you: one directory holds the graph's snapshots and its write-ahead log, every commit is durable before it returns, and opening the directory brings the graph back exactly as the last commit left it, also after a crash. One directory = one graph, one writer: "SQLite for graphs".

The directory is usually a path on disk. It can also be one single file (graph.gstore, for embedded use) or live in memory (every target, the browser included).

#![allow(unused)]
fn main() {
use graphersal::persist::{RecoveryTarget, Store, StoreOptions};

let store = Store::create("data", StoreOptions::new())?;      // or Store::open("data", ..)
store.graph().write().traversal_mut().add_v("person").property("name", "ann").to_list()?;
store.mark("after-ann")?;                                      // a point-in-time target
store.checkpoint(Some("nightly"))?;                            // a snapshot of the current state
store.close()?;                                                // a clean close (Drop does it too)

let past = Store::open_read_only("data", RecoveryTarget::Mark("after-ann".into()))?;
assert_eq!(past.graph.vertex_count(), 1);
Ok::<(), Box<dyn std::error::Error>>(())
}
graphersal store create data/ --from modern     # the same from the command line
graphersal --graph data/ -e 'g.add_v("person").property("name", "ann").to_list()'
graphersal store info data/

The files

data/
├── GRAPH, GRAPH.copy        identity, lineage, state (two copies of the same small file)
├── LOCK                     held by the one writer (an operating-system lock)
├── BACKUP                   the backup pin: held shared while a backup copies, prune waits for it
├── INTENT                   only while a multi-file operation runs (rollback, prune, ...)
├── marks                    the marks, rebuilt from the WAL when lost
├── snapshots/
│   └── 00000000000000000003/      one snapshot, named by its commit (20 digits)
│       ├── manifest               identity, position, counts, the segment list, every chunk's checksum
│       ├── schema.json            the stored schema
│       ├── v-000000.seg           vertices, sorted by id, in compressed chunks
│       └── e-000000.seg           edges
├── wal/
│   └── 00000000000000000004.wal   write-ahead log segments, named by their first commit (16 MiB each)
└── attic/
    └── 20261008T070510Z-00000000000000000002/   history a rollback moved aside
FileWhat it isWhen it changes
GRAPH, GRAPH.copythe store's identity (graph_id, the lineage chain of forks and rollbacks it descends from), its state (closed cleanly or open), the chunk size, and a few recovery facts (the newest WAL segment, the commit of the last clean close, a backup marker)rarely: open, close, a new WAL segment, a rollback; always both copies, each written atomically
LOCKthe writer's operating-system lock (flock / LockFileEx), released when the process endscreated empty by the first writer, never written
BACKUPa lock file: backups hold it shared while they copy, prune takes it exclusivelycreated empty by the first backup or prune, never written
INTENTthe plan of a multi-file operation in progress; an interrupted one is completed at the next openduring a rollback, an attic restore, a prune, a backup increment (in the backup)
marksthe list of marks, so listing them reads no WALafter every mark
snapshots/<commit>/full copies of the graph; immutable once writtena checkpoint adds one, prune removes old ones
wal/<first commit>.walevery commit since the oldest snapshot, in order; append-onlyevery commit and mark appends a record
attic/<time>-<commit>/snapshots and WAL that an in-place rollback moved aside, with an ATTIC descriptiona rollback adds an entry; restore and remove take it away

Every name is relative to the store's root, and no file records a path: move or copy the whole directory and open it there. The exact byte layouts are the Storage Format Specification.

The pages of the Store

Page
Opening and Closingcreate, open, recovery on open, the lock, read-only opens, clean close and Ctrl+C
Commits and Durabilityfsync per commit, Durability, refused records, the durable end, change data capture
Checkpoints and Snapshotsmanual and automatic checkpoints, the chunk size, .gsnap export and import
Marksnaming a position; snapshots vs marks
Point in Timetargets (commit, time, mark), the time format, read-only views
Forka past state as a new, writable store
Rollback and the Atticgoing back in place, and undoing it
Backup and Restorefull, incremental and ZIP backups, restore
Prune, Compact and Retentionremoving old history explicitly; giving disk space back
Verifythe scrub
Damage, Maintenance and Repairwhat happens when the disk rots; the runbook
Format Versions and Upgrade
Directory BackendsFsDir, MemDir, your own StoreDir; relocation and split stores
Single-File Storethe whole store in one .gstore file
Your Own StorageStore<S> over a host's PersistentStorage: open_in, create_in
Creation Parameterswhat is fixed when a store is created (store id, damage policy, chunk size, backend, format version) and the per-open options

What works while the store is open elsewhere (a dev server, a REPL, another program):

While another process writes
info, list, marks, verify, fork, backup, export, repair, compact-advice, attic (list, changes, fork); Store::open_read_only, Store::inspectwork (no lock taken)
mark, checkpoint, prune, compact, rollback, attic restore/remove, convert; Store::openrefused: "in use" (PersistError::Locked); use the dev server's Store menu, or stop the writer