Opening and Closing
Create
#![allow(unused)] fn main() { use graphersal::prelude::*; use graphersal::persist::{Store, StoreOptions}; let empty = Store::create("data", StoreOptions::new())?; // an empty graph let modern = Store::create_with("modern-data", TraversalGraph::tinkerpop_modern(), StoreOptions::new())?; let file = std::io::BufReader::new(std::fs::File::open("m.gsnap")?); let restored = Store::create_from_packed("from-gsnap", file, StoreOptions::new())?; // from a .gsnap Ok::<(), Box<dyn std::error::Error>>(()) }
The directory must be missing or empty. A new store has one snapshot (commit 0, the initial
graph) and an empty WAL, and is open read-write when create returns. A path ending in
.gstore (or Store::create_file) makes a single-file store.
StoreOptions::with_chunk_bytes sets the snapshot chunk size, which is fixed for the store's
life (Checkpoints).
graphersal store create data/ --from modern # --from: modern, empty, large, tree, a GraphSON/GraphML file, a .gsnap
graphersal store create small-chunks/ --chunk-size 262144 # 256 KiB chunks
graphersal --graph new/ --create-store -e 'g.add_v("person").to_list()' # create when missing ("note: created the store new/ (damage policy maintenance)")
Open: recovery on open
Store::open(dir, options) opens a store read-write and always runs its recovery:
- takes the writer lock (a second writer, in this or another process, gets
PersistError::Locked: "the store ... is in use"); - completes an interrupted multi-file operation (an
INTENTfile: a rollback, an attic restore, a prune) and removes temporary files of an interrupted checkpoint; - checks the set of WAL segments against
GRAPH(a lost, emptied or replaced newest segment is damage, never a shorter history); - loads the latest snapshot and replays the WAL after it;
- cuts an interrupted last write of the WAL (a torn tail) and rebuilds a lost
marksfile; - marks the store as open and attaches the journal: from now on every commit is durable.
store.open_report() says what happened: whether the last close was clean (clean_close), the
snapshot it started from and the commits replayed, a cut torn tail, a completed intent, removed
temporary files, a repaired GRAPH copy, warnings. A store that was not closed cleanly (a
crash, kill -9, a power cut) is not damaged: the WAL replay brings back every commit; the CLI
then notes "not closed cleanly", which is harmless.
$ graphersal --graph data/ -e 'g.v().count().next()'
9
Damage never fails the open and is never skipped silently: the store opens read-only in
maintenance mode with a damage report. A backup directory is refused by
Store::open (PersistError::IsBackup): it opens read-only through Store::open_backup until
it is restored. A store that holds something critical this build
does not know (a critical definition of an unknown kind, written by a newer Graphersal) opens
read-only too: Store::read_only_reason() says why, OpenReport::read_only records it, and
every write is refused with PersistError::ReadOnly (Format Versions).
In the CLI, --graph <path> opens a Store when the path is a directory, ends in /, is a
missing path without an extension, ends in .gstore, or is an existing single-file store.
--create-store creates it when it does not exist yet (missing or empty directory).
Read without opening
None of these take the writer lock; all work while a writer has the store open:
Store::inspect(dir) | the StoreInfo: identity, lineage, position, snapshots, marks, WAL size, attic, state (graphersal store info) |
Store::snapshots_dir(dir), Store::marks_dir(dir) | the listings (store list, store marks) |
Store::open_read_only(dir, target) | the graph at any point in time, as an unpublished TraversalGraph |
Store::verify_dir(dir) | the scrub |
Store::is_store(dir) | whether dir holds a store |
Close
store.close() (and Drop) closes cleanly: it syncs, records the last commit and marks the
store "closed cleanly" in GRAPH, and releases the lock. After close() the shared graph
refuses commits (another holder of the Arc<Graph> gets an error, not a silent loss).
store.shutdown() does the same through a shared reference. With
StoreOptions::with_checkpoint_on_close(true) a checkpoint is written first, so the next open
replays nothing.
Ctrl+C (SIGINT, SIGTERM) in the CLI, its REPL and the dev server closes an open store
cleanly: the running query is cancelled first (it rolls back), then the store is closed. A second
Ctrl+C exits at once (harmless too: every commit is already durable). The dev server exits with
0, the REPL and -e runs with 130.
Stopping: closing the store data/ (Ctrl+C again to exit at once)...
The store data/ was closed cleanly.
Locking
One store has at most one writer: an operating-system lock on LOCK (on a single file, the lock
on the file itself; in memory, an in-process flag), released by the operating system when the
process ends, so a crashed writer never leaves a stale lock. Readers (inspect, listings,
open_read_only, verify, fork, backups, export) take no lock. Two Store values of the
same directory in one process are two writers: the second gets PersistError::Locked too.