Clone and convert a store
Copy a relate store with relate clone between a .rl file and a directory and between the parquet, json and markdown codecs, and check the copy matches.
cli
Before you start: Install relate
This guide shows you how to copy a relate store A relate database: a directory, a single .rl file, or :memory:, opened with --database. Read more in the glossary into a new one, change its layout or its codec on the way, and check that the copy holds the same graph. The command is relate clone <source> <dest>: the destination's path picks the layout (a path ending in .rl is one file, any other path a directory store A store kept as a folder of snapshot files, a WAL folder and lock files; the layout for any --database path that is not :memory: and does not end in .rl. Read more in the glossary), and --to-snapshot-format parquet|json|markdown picks the codec. The source is opened read-only and never written.
Prerequisites
- relate on your
PATH(Install relate). - A store to copy. The commands on this page start from the shared example graph in a single-file store,
team.rl:
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.cypherDecide when to convert
A store's layout and codec The on-disk encoding of a store's snapshot: Parquet (the default), JSON or markdown, fixed when the store is created. Read more in the glossary are fixed when it is created: --snapshot-format applies only to an empty directory. To change either, write a new store with clone relate clone: the command that copies a store into a new one, optionally in another codec, reading the source without writing it. Read more in the glossary:
| If | Convert to | Command |
|---|---|---|
| A second process must read while one writes, or follow the writer | A directory store | relate clone ./team.rl ./team |
| The store is attached, shipped, or archived as one file | A .rl file | relate clone ./team ./team.rl |
| Reviewers diff the graph in pull requests | A directory store with the json codec | relate clone --to-snapshot-format json ./team ./team-json |
| People read and edit the graph as notes | A directory store with the markdown codec | relate clone --to-snapshot-format markdown ./team ./team-md |
A .rl file is far larger than what it holds | A fresh .rl file | relate clone ./team.rl ./team-compact.rl |
The storage formats reference has the files each layout and codec writes.
Change the layout
Clone the .rl file into a directory, then the directory back into a second .rl file. relate clone prints nothing on success:
relate clone ./team.rl ./team
relate clone ./team ./team-copy.rlThe destination must not exist yet; relate clone never overwrites. It reads the state committed when it opens the source and does not follow later commits. A directory source can be cloned while another process writes to it; a .rl The file extension of a single-file store; a --database path that ends in .rl selects that layout. Read more in the glossary only while no process holds it open for writing.
Change the codec
Without --to-snapshot-format, the destination keeps the source's codec. With it, the destination's first checkpoint is written in the codec it names. Write a json copy and a markdown copy of the directory store:
relate clone --to-snapshot-format json ./team ./team-json
relate clone --to-snapshot-format markdown ./team ./team-mdrelate info reads a store's layout and codec without opening it:
relate info --database ./team-md | head -n 2layout: native directory
codec: markdownThe json copy holds the graph in one graph.json. The markdown copy holds one note per node, in a folder per label, with an index note per folder, a root index.md and a log.md:
ls team-json team-md team-md/Personteam-json:
graph.json
manifest.json
wal
team-md:
Person
Project
index.md
log.md
team-md/Person:
Ada.md
Grace.md
Noor.md
index.mdThe markdown codec names each note after a key property of its label, name here, so every node of a label needs that property, and its value must be unique within the label. --markdown-body-properties chooses the properties written as each note's body; Open a store as an Obsidian vault covers both.
Check that the copy holds the same graph
Compare what a statement returns from the source and from the copy. This check writes every node's labels and properties and every relationship to a file, sorted, once for the source and once for a store that went from .rl to markdown and back to a parquet .rl file. The jsonl mode ends each statement with a line of its timings, which change on every run, so grep -v '"timings"' drops those lines before the comparison:
cat > graph-check.cypher <<'EOF'
MATCH (n) RETURN labels(n) AS labels, properties(n) AS props ORDER BY n.name;
MATCH (a)-[r]->(b) RETURN a.name AS from, type(r) AS type, properties(r) AS props, b.name AS to ORDER BY from, type, to;
EOF
relate clone --to-snapshot-format parquet ./team-md ./team-roundtrip.rl
relate query --database ./team.rl --read-only --mode jsonl < graph-check.cypher | grep -v '"timings"' > before.jsonl
relate query --database ./team-roundtrip.rl --read-only --mode jsonl < graph-check.cypher | grep -v '"timings"' > after.jsonl
cmp before.jsonl after.jsonl && echo "same graph: $(wc -l < after.jsonl | tr -d ' ') rows"same graph: 10 rowsFive nodes and five relationships come back identical. The way back names its codec: in relate 0.1.2, a .rl destination refuses the body-property configuration a markdown store records, so relate clone ./team-md ./team-roundtrip.rl without --to-snapshot-format, or with --to-snapshot-format markdown, exits 1 with Failed to clone database: semantic error: markdown body properties are set but the store's snapshot codec is parquet, not markdown; the configuration would be ignored. A markdown store clones into a .rl file with --to-snapshot-format parquet or json, and into a directory store with any codec. Order the rows by a property your graph has on every node. Values without a YAML form keep their type in a markdown note: a date or a point is written as a small map tagged __type and reads back as a date or a point. One difference to expect from a markdown round trip: a node with two or more labels can return them from labels(n) in another order, so sort them before comparing such a graph.
Measure the sizes
A .rl file grows to the most space it has needed and reuses it; it does not shrink when data is deleted. relate clone writes only the current state. To see it, add 20,000 draft nodes to team.rl, delete them again, and clone it:
relate info --database ./team.rl | grep 'total bytes'
relate --database ./team.rl --mode envelope "UNWIND range(1, 20000) AS i CREATE (:Draft {id: i, text: 'draft note ' + toString(i)})"
relate --database ./team.rl --mode envelope "MATCH (d:Draft) DELETE d"
relate clone ./team.rl ./team-compact.rltotal bytes: 36864
{"columnTypes":[],"columns":[],"notifications":[],"planCache":"miss","rows":[],"timings":{"elapsed_ms":66.759917,"execute_ms":66.566792,"lex_parse_ms":0.006292,"lower_ms":0.007292,"parse_ms":0.182708,"plan_ms":0.005332999999999999,"result_generation_ms":0.000125,"rewrite_ms":0.000375},"truncated":false}
{"columnTypes":[],"columns":[],"notifications":[],"planCache":"miss","rows":[],"timings":{"elapsed_ms":21.065709,"execute_ms":20.903374999999997,"lex_parse_ms":0.002333,"lower_ms":0.0029579999999999997,"parse_ms":0.154167,"plan_ms":0.003583,"result_generation_ms":0.000417,"rewrite_ms":0.00020800000000000001},"truncated":false}The method: relate info prints each store's size on disk as total bytes, read without opening the store. The loop prints it for the bloated file, its clone, and the three copies made in Change the codec and Change the layout:
for store in team.rl team-compact.rl team team-json team-md; do printf '%s: ' "$store"; relate info --database "./$store" | grep 'total bytes'; doneteam.rl: total bytes: 3203072
team-compact.rl: total bytes: 32768
team: total bytes: 3556
team-json: total bytes: 1385
team-md: total bytes: 2926| Store | Measured | What it shows |
|---|---|---|
team.rl before the drafts | 36,864 bytes | A new .rl file for 5 nodes and 5 relationships |
team.rl after the drafts were deleted | 3,203,072 bytes | The file kept the space the 20,000 drafts needed |
team-compact.rl | 32,768 bytes | The clone holds only the current state |
team (parquet directory) | 3,556 bytes | Columnar files plus a manifest |
team-json | 1,385 bytes | One JSON file plus a manifest; on this graph the smallest |
team-md | 2,926 bytes | One note per node, an index per label, and the log.md and index.md notes |
These numbers are for this 5-node graph. At 100,000 nodes the json codec is about 3.4 times the size of parquet (measured in the formats reference), so the sizes on a small graph don't predict the order on a large one.
Verify
The converted store opens and answers statements. The markdown copy returns who works on what:
relate --database ./team-md --read-only --mode envelope "MATCH (p:Person)-[:WORKS_ON]->(proj:Project) RETURN p.name AS name, proj.name AS project ORDER BY name, project"{"columnTypes":["STRING","STRING"],"columns":["name","project"],"notifications":[],"planCache":"miss","rows":[{"name":"Ada","project":"relate"},{"name":"Grace","project":"opencypher-tck"},{"name":"Grace","project":"relate"}],"timings":{"elapsed_ms":0.55875,"execute_ms":0.382,"lex_parse_ms":0.033417,"lower_ms":0.039709,"parse_ms":0.1085,"plan_ms":0.058542000000000004,"result_generation_ms":0.000375,"rewrite_ms":0.008291999999999999},"truncated":false}If it goes wrong
-
Error: unexpected argument to clone: --snapshot-format(exit code 2). The flag onrelate cloneis--to-snapshot-format:relate clone --snapshot-format json ./team ./team-json2Error: unexpected argument to clone: --snapshot-format Did you mean '--to-snapshot-format'? Usage: relate clone [--wal-dir DIR] [--to-snapshot-format F] [--markdown-body-properties LIST] SOURCE DEST Run 'relate clone --help' for more information.A
--snapshot-formatplaced before the verb, as inrelate --snapshot-format json clone …, does not change the destination's codec: the copy keeps the source's. Put--to-snapshot-formatafterclone. -
Failed to clone database: semantic error: markdown body properties are set but the store's snapshot codec is parquet, not markdown; the configuration would be ignored(exit code 1). The destination is a.rlpath and the source is amarkdownstore, or the command passes--markdown-body-properties. In relate 0.1.2 a.rldestination refuses that configuration, even with--to-snapshot-format markdown. Pass--to-snapshot-format parquetorjsonfor a.rlcopy of a vault, or clone it into a directory store. -
Failed to clone database: storage error: clone destination already exists(exit code 1).relate clonenever overwrites. Pick a new destination path. -
Failed to clone database: storage error: markdown vault: node <id> of label <Label> has no '<property>' property; the markdown vault requires a non-null unique '<property>' on :<Label>(exit code 1). Amarkdowndestination names each note after a key property, and a node of that label lacks it. Set the property on every node of the label first. A failed clone removes the destination it created. -
Failed to clone database: storage error: markdown vault: two nodes of label <Label> share the key value '<value>'; the vault filename would collide(exit code 1). Two nodes of one label have the same key value. Make the values unique, or keep the store inparquetorjson. -
Error: invalid --to-snapshot-format: storage error: unknown snapshot codec '<value>' (expected parquet, json, markdown or rkyv)(exit code 2). The codec name is misspelled. -
Failed to clone database: storage locked: <path>(exit code 1). The source is a.rlfile that a process holds open for writing. Clone it when that process exits (storage locked).
Related
relate clone: the command's flags and exit codes.- Storage formats: the files each layout and codec writes, and sizes at 100,000 nodes.
- Choose a store layout: which layout and codec fit a job.
- Back up and restore a store:
relate cloneas the backup command. - Single-file graph in the capability catalog.