Docs
View as markdown

Choose a durability mode

Pick full, everysecond, or snapshot durability for a relate store, see what each can lose in a crash, and set it per run, environment, or server.

clipythonnode

Before you start: Install relate

This guide shows you how to choose and set a durability mode When a commit counts as safe on disk: full (the default) syncs the WAL first, everysecond about once a second, and snapshot only at checkpoints. Read more in the glossary: the rule for when a commit counts as safe on disk. The default, full, syncs the WAL Append-only log each commit goes to before it applies; under the default full durability mode it is synced first, so a crash loses no acknowledged write. Read more in the glossary to disk before a commit returns, so a crash loses no acknowledged write. everysecond and snapshot return before that sync, and a crash can lose writes they already acknowledged.

Prerequisites

  • relate on your PATH (Install relate).
  • A store A relate database: a directory, a single .rl file, or :memory:, opened with --database. Read more in the glossary on disk, a directory or a .rl file. An in-memory store forgets everything at exit in every mode.

Compare the modes

To sync is to force written data out of the operating system's cache onto the disk (fsync). Each mode decides when the WAL is synced relative to the moment a commit returns:

ModeA commit returnsThe WAL is syncedA crash can lose
full (default)After its WAL record is syncedBefore each commit, or group of commits, is acknowledgedNo acknowledged write
everysecondAfter it applies in memoryAbout once a second, in the backgroundAbout the last second of acknowledged writes; slow storage or a delayed sync makes the window longer
snapshotAfter it applies in memoryNever on commit; only a completed checkpoint is durableEvery write since the last completed checkpoint, up to 300 seconds of writes under the default timer

A killed process can lose the window too, not only a power cut. In a test on this release, a write to a .rl store was acknowledged and the process was killed with kill -9 half a second later, three times per mode. Under full the write was there on the next open every time; under everysecond and snapshot it was gone every time. In one more snapshot run, it was still gone with 3 seconds between the write and the kill.

The mode is a setting of the process that opens the store, not of the store. The next open picks its own, and an open that sets nothing gets full.

Each mode's loss window for one crash, a few seconds after a completed checkpoint.

What a crash can lose. The durability mode decides when a commit is acknowledged relative to the WAL sync. It is a setting of the process that opens the store (--durability-mode or RELATE_DURABILITY_MODE, default full), not of the store.

The picture's timeline: 8 writes are acknowledged in the 4.6 s after a completed checkpoint, then the process crashes. A killed process can lose the window as well as a power cut.

ModeThe WAL is syncedAcknowledged writes the crash at 4.6 s can lose
fullBefore each commit, or group of commits, is acknowledgedNone: all 8 come back
everysecondAbout once a second, in the backgroundThe 2 writes after the last sync, at 4 s
snapshotNever on commit; only a completed checkpoint is durableAll 8, every write since the checkpoint

Under everysecond, slow storage or a delayed sync makes the window longer than about a second. Under snapshot, the window runs until the next completed checkpoint: up to 300 seconds of writes under the default timer, fewer when a WAL segment fills first.

What each durability mode can lose in a crash, as relate documents itWhat a crash 4.6 s after a checkpoint can lose: Acknowledged writes on a ten-second timeline after a completed checkpoint, with a crash at 4.6 s. Under full, all 8 writes acknowledged before the crash come back: each returned only after its WAL record was synced. Under everysecond, the last background sync was at 4 s, so the 2 writes acknowledged in the 0.6 s after it can be lost. Slow storage or a delayed sync makes the window longer. Under snapshot, nothing is synced on commit, so all 8 writes acknowledged since the last completed checkpoint can be lost. The next timed checkpoint is due at 300 s; a WAL segment that fills, or a clean close, checkpoints sooner.What a crash 4.6 s after a checkpoint can losecrash at 4.6 sfullno acknowledged write losteverysecondcan lose 2 writessnapshotcan lose 8 writes012345678910seconds since the last completed checkpointcompleted checkpointacknowledged, survivesacknowledged, can be lostbackground sync
What each durability mode can lose in a crash, as relate documents itWhat a crash 4.6 s after a checkpoint can lose: Acknowledged writes on a ten-second timeline after a completed checkpoint, with a crash at 4.6 s. Under full, all 8 writes acknowledged before the crash come back: each returned only after its WAL record was synced. Under everysecond, the last background sync was at 4 s, so the 2 writes acknowledged in the 0.6 s after it can be lost. Slow storage or a delayed sync makes the window longer. Under snapshot, nothing is synced on commit, so all 8 writes acknowledged since the last completed checkpoint can be lost. The next timed checkpoint is due at 300 s; a WAL segment that fills, or a clean close, checkpoints sooner.What a crash 4.6 s after a checkpoint can losecrash at 4.6 sfullno acknowledged write losteverysecondcan lose 2 writessnapshotcan lose 8 writes012345678910seconds since the last completed checkpointcompleted checkpointacknowledged, survivesacknowledged, can be lostbackground sync
What each durability mode can lose in a crash, as relate documents it

Live: Commit, Checkpoint and Crash step through what relate does with the WAL, the in-memory graph and the snapshot (inside one .rl file in the file view), and a mode switch with a crash slider shades what full, everysecond and snapshot can lose; under reduced motion each step waits for Next. It is a diagram of documented behaviour: no engine runs.

Choose one

  • If every acknowledged write must survive a crash, keep full. It is the only mode with no loss window.
  • If losing about the last second of writes is acceptable, such as a cache you can rebuild, choose everysecond. Commits stop waiting for each sync.
  • If the writes can be redone from their source, such as a bulk load you can rerun, choose snapshot. Pair it with a checkpoint interval that bounds what a rerun has to redo.

Set it for one run

--durability-mode applies to the run it's on. To follow along, create the shared example graph as team.rl first:

cat > team.cypher <<'EOF'
CREATE (ada:Person {name: 'Ada', role: 'engineer'}),
       (grace:Person {name: 'Grace', role: 'engineer'}),
       (noor:Person {name: 'Noor', role: 'maintainer'}),
       (relate:Project {name: 'relate', language: 'Rust'}),
       (tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
       (ada)-[:WORKS_ON {since: 2024}]->(relate),
       (grace)-[:WORKS_ON {since: 2023}]->(relate),
       (grace)-[:WORKS_ON {since: 2022}]->(tck),
       (noor)-[:MAINTAINS]->(relate),
       (ada)-[:KNOWS]->(grace);
EOF
relate query --database ./team.rl < team.cypher

This run writes to team.rl under everysecond: it records that Noor maintains opencypher-tck too.

relate --database ./team.rl --durability-mode everysecond --mode envelope "MATCH (noor:Person {name: 'Noor'}), (tck:Project {name: 'opencypher-tck'}) CREATE (noor)-[:MAINTAINS]->(tck) RETURN noor.name AS person, tck.name AS project"
{"columnTypes":["STRING","STRING"],"columns":["person","project"],"notifications":[],"planCache":"miss","rows":[{"person":"Noor","project":"opencypher-tck"}],"timings":{"elapsed_ms":0.657292,"execute_ms":0.431542,"lex_parse_ms":0.006583,"lower_ms":0.011,"parse_ms":0.212583,"plan_ms":0.008417,"result_generation_ms":0.00020800000000000001,"rewrite_ms":0.000375},"truncated":false}

A one-shot run checkpoints when it exits cleanly, so this write is on disk by the time the command returns. The window matters for processes that stay open, such as relate serve, an app that uses a client, or a long script.

Set it for an environment

RELATE_DURABILITY_MODE sets the mode for every open that doesn't set one itself. The flag wins over the variable. The Python and Node.js clients read the same variable when they open a store, so it is how an app chooses a mode.

This run writes to team.rl under snapshot, set by the variable: Grace maintains relate now.

RELATE_DURABILITY_MODE=snapshot relate --database ./team.rl --mode envelope "MATCH (grace:Person {name: 'Grace'}), (r:Project {name: 'relate'}) CREATE (grace)-[:MAINTAINS]->(r) RETURN grace.name AS person, r.name AS project"
{"columnTypes":["STRING","STRING"],"columns":["person","project"],"notifications":[],"planCache":"miss","rows":[{"person":"Grace","project":"relate"}],"timings":{"elapsed_ms":0.6042909999999999,"execute_ms":0.364666,"lex_parse_ms":0.008749999999999999,"lower_ms":0.011375,"parse_ms":0.224375,"plan_ms":0.009792,"result_generation_ms":0.00041600000000000003,"rewrite_ms":0.000458},"truncated":false}

Set it for a server

relate serve takes the same flag, with the same order: the flag, then RELATE_DURABILITY_MODE, then full. This server is writable on purpose, since the mode only matters for writes:

relate serve --database ./team.rl --http 7474 --durability-mode everysecond

Bound the snapshot window

Under snapshot, the time between checkpoints is the loss window. A one-shot run or relate query takes --checkpoint-interval-secs N; relate serve doesn't accept that flag and takes only RELATE_CHECKPOINT_INTERVAL_SECS. The default is 300 seconds. 0 turns off only the timer: checkpoints still run when a WAL segment fills, on a clean exit, and on .checkpoint.

RELATE_CHECKPOINT_INTERVAL_SECS=30 relate serve --database ./team.rl --http 7474 --durability-mode snapshot

Verify

Read the store back read-only. Both writes are there, next to the example's original MAINTAINS relationship:

relate --database ./team.rl --read-only --mode envelope "MATCH (p:Person)-[:MAINTAINS]->(project:Project) RETURN p.name AS person, project.name AS project ORDER BY person, project"
{"columnTypes":["STRING","STRING"],"columns":["person","project"],"notifications":[],"planCache":"miss","rows":[{"person":"Grace","project":"relate"},{"person":"Noor","project":"opencypher-tck"},{"person":"Noor","project":"relate"}],"timings":{"elapsed_ms":0.40049999999999997,"execute_ms":0.22204200000000002,"lex_parse_ms":0.033457999999999995,"lower_ms":0.040958,"parse_ms":0.109625,"plan_ms":0.059834,"result_generation_ms":0.000209,"rewrite_ms":0.008208},"truncated":false}

If it goes wrong

  • Error: invalid --durability-mode: semantic error: invalid durability mode "<value>"; expected full, everysecond, or snapshot, exit code 2. The flag's value is misspelled. Use one of the three names.
  • Error: invalid RELATE_DURABILITY_MODE: semantic error: invalid durability mode "<value>"; expected full, everysecond, or snapshot, exit code 2. The variable holds something else. Fix it or unset it (the variable's error). The clients refuse to open too: Python's relate.connect() raises OperationalError and Node.js throws, each with a message that names RELATE_DURABILITY_MODE.
  • Writes acknowledged just before a crash are missing. The process ran under everysecond or snapshot, and the crash fell inside the loss window. Switch to full if those writes matter (Recover from a crash).