---
title: Choose a store layout
description: "relate, the embedded Cypher graph database (relatedb.ai): Pick an in-memory store, a directory store or a single .rl file, and a codec, by their trade-offs in size, startup, diffs and sharing."
type: how-to
surfaces:
  - cli
  - python
  - node
status: stable
verified: 0.1.2
verified_by:
  - python
  - node
  - cli
samples:
  - https://relatedb.ai/docs/guides/storage/choose-a-store-layout/samples/open-layouts.json
  - https://relatedb.ai/docs/guides/storage/choose-a-store-layout/samples/open-layouts-js.json
canonical_url: https://relatedb.ai/docs/guides/storage/choose-a-store-layout/
prerequisites:
  - get-started/install
related:
  - reference/storage/formats
  - guides/storage/backup-and-restore
  - guides/operate/two-processes-one-store
  - concepts/storage-and-durability
  - guides/storage/clone-and-convert
  - guides/storage/read-only-and-follow
last_updated: 2026-11-09
---

# Choose a store layout

Pick an in-memory store, a directory store or a single .rl file, and a codec, by their trade-offs in size, startup, diffs and sharing.

> Verified against relate 0.1.2. If your `relate --version` is newer, read https://relatedb.ai/docs/reference/changelog/#since-0.1.2

This guide shows you how to choose where a relate [store](https://relatedb.ai/docs/reference/glossary/#store) lives and how it is written to disk. The path you pass to `--database` picks the layout: `:memory:` keeps the graph in the process, a path ending in `.rl` is one file, and any other path is a directory. Use `:memory:` for throwaway work, a `.rl` file for a store you copy or hand to someone, and a [directory store](https://relatedb.ai/docs/reference/glossary/#directory-store) when a second process reads beside the writer or you want a text snapshot you can diff.

## Prerequisites

- relate on your `PATH` ([Install relate](https://relatedb.ai/docs/get-started/install/)).
- For the client section: the Python client (`relate-db`) or the Node.js client (`@relate/relate`).

## Compare the layouts

Every layout runs the same engine: the whole graph lives in memory while the store is open, and every commit goes to the store's write-ahead log ([WAL](https://relatedb.ai/docs/reference/glossary/#wal)) first. The layouts differ in what sits on disk.

|                          | In memory                         | `.rl` file                                                                                | Directory store                                                                                 |
| ------------------------ | --------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `--database` value       | `:memory:` (the default)          | a path ending in `.rl`                                                                    | any other path                                                                                  |
| Survives the process     | No                                | Yes                                                                                       | Yes                                                                                             |
| On disk                  | Nothing                           | One file                                                                                  | A folder: snapshot files and a WAL folder                                                       |
| A reader beside a writer | Not possible: one process owns it | No: a `--read-only` open is refused while a writer holds the file                         | Yes, with `--read-only`                                                                         |
| Size on disk             | None                              | Grows to the most space it has needed and reuses it; `relate clone` writes a compact copy | The snapshot plus the WAL since the last checkpoint                                             |
| Diffable                 | No                                | No                                                                                        | With the JSON [codec](https://relatedb.ai/docs/reference/glossary/#codec): `graph.json` is text |
| Startup                  | Starts empty                      | Loads the whole graph, then replays the WAL                                               | Loads the whole graph, then replays the WAL                                                     |

Startup grows with the size of the graph in every durable layout, because relate loads all of it into memory before the first statement runs. The [storage formats reference](https://relatedb.ai/docs/reference/storage/formats/) has the file layout of each and measured sizes.

## Start in memory

With no `--database`, a statement runs against an in-memory store that is gone when the process exits:

```bash verify
relate --mode envelope "RETURN 1 AS ok"
```

```text
{"columnTypes":["INTEGER"],"columns":["ok"],"notifications":[],"planCache":"miss","rows":[{"ok":1}],"timings":{"elapsed_ms":0.356541,"execute_ms":0.20325000000000001,"lex_parse_ms":0.020542,"lower_ms":0.02775,"parse_ms":0.09895799999999999,"plan_ms":0.042292,"result_generation_ms":0.000209,"rewrite_ms":0.007292},"truncated":false}
```

Use it for tests, scratch statements and anything you rebuild from a script.

## Create a single-file store

The rest of this guide uses one small graph. Save its seed statement as `team.cypher`:

```bash verify
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
```

Run it into a path that ends in `.rl`. relate creates the file on the first open:

```bash verify
relate query --database ./team.rl --mode envelope < team.cypher
```

```text
{"columnTypes":[],"columns":[],"notifications":[],"planCache":"miss","rows":[],"timings":{"elapsed_ms":2.00025,"execute_ms":1.69775,"lex_parse_ms":0.01425,"lower_ms":0.02775,"parse_ms":0.283083,"plan_ms":0.013583,"result_generation_ms":0.000167,"rewrite_ms":0.000583},"truncated":false}
```

`relate info` reads a store's metadata without opening it:

```bash verify
relate info --database ./team.rl
```

```text
layout: .rl bundle
superblock version: 1
codec: parquet
format version: 3
checkpoint LSN: 1
commit timestamp: 1
total bytes: 36864
wal segments past checkpoint: 1
nodes (at checkpoint LSN 1): 5
relationships (at checkpoint LSN 1): 5
indexes: 0
constraints: 0
```

What sits inside `team.rl`, and what a commit and a checkpoint change there.

**One `.rl` file.** A single-file store keeps in one file the same tree a directory store spreads over a folder, WAL included. Its parts, in logical order rather than by byte position:

| Part            | Holds                                                                                                                                                     |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Superblock      | The container's version marker: magic `MDBCLB01`, version `1` (`relate info` prints `superblock version: 1`)                                              |
| Snapshot        | `manifest.json` and the codec's files (with the default `parquet` codec, the files under `nodes/` and `relationships/`): the state at the last checkpoint |
| WAL             | `wal/NNNNNNNN.log` segments: every commit since the last checkpoint                                                                                       |
| Space it reuses | The most space the snapshot and WAL have needed at once; later writes reuse what they no longer hold, and the file does not shrink                        |

A commit appends its record to the open segment inside the file. A checkpoint writes the new snapshot files inside it, publishes them with `manifest.json` last, and deletes the segments the snapshot covers. A writer holds the file itself, with no lock files beside it, so a `--read-only` open is refused while a writer has it, and `--wal-dir` is refused for a `.rl` path. `relate clone` writes a compact copy that holds only the current state.

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.

![The parts of one .rl file, as relate documents it](https://relatedb.ai/docs/guides/storage/choose-a-store-layout/element-rl-file-light.svg)

Choose a `.rl` file when the store travels: one file to copy, attach, or check in. To read it into memory without holding the file, pass it to `--load`; the in-memory copy is read-only:

```bash verify
relate --load ./team.rl --mode envelope "MATCH (p:Person) RETURN count(p) AS people"
```

```text
{"columnTypes":["INTEGER"],"columns":["people"],"notifications":[],"planCache":"miss","rows":[{"people":3}],"timings":{"elapsed_ms":0.363917,"execute_ms":0.18412499999999998,"lex_parse_ms":0.03225,"lower_ms":0.043709,"parse_ms":0.11108299999999999,"plan_ms":0.060209,"result_generation_ms":0.00025,"rewrite_ms":0.00825},"truncated":false}
```

## Create a directory store

Any path that does not end in `.rl` becomes a directory store:

```bash verify
relate query --database ./team --mode envelope < team.cypher
```

```text
{"columnTypes":[],"columns":[],"notifications":[],"planCache":"miss","rows":[],"timings":{"elapsed_ms":1.223083,"execute_ms":0.926084,"lex_parse_ms":0.015042,"lower_ms":0.021459,"parse_ms":0.28016599999999997,"plan_ms":0.010917000000000001,"result_generation_ms":0.000209,"rewrite_ms":0.0005},"truncated":false}
```

The folder holds the snapshot (`manifest.json` and the Parquet files under `nodes/` and `relationships/`) and the WAL:

```bash verify
ls team
```

```text
manifest.json
nodes
relationships
wal
```

Choose a directory store when another process needs to read while one writes: a `--read-only` open of a directory works beside its writer, and a `.rl` file refuses it. [Share one store between two processes](https://relatedb.ai/docs/guides/operate/two-processes-one-store/) covers that setup.

## Pick a codec for a directory store

A codec is the encoding a checkpoint writes the snapshot in. The default is Parquet: compact, binary columnar files. JSON writes the whole graph as one readable `graph.json`, which a code review can diff. Pass `--snapshot-format json` when you create the store:

```bash verify
relate query --database ./team-json --snapshot-format json --mode envelope < team.cypher
```

```text
{"columnTypes":[],"columns":[],"notifications":[],"planCache":"miss","rows":[],"timings":{"elapsed_ms":1.260125,"execute_ms":0.930708,"lex_parse_ms":0.013375,"lower_ms":0.061,"parse_ms":0.313042,"plan_ms":0.010166000000000001,"result_generation_ms":0.000042,"rewrite_ms":0.001084},"truncated":false}
```

```bash verify
ls team-json
```

```text
graph.json
manifest.json
wal
```

The codec is fixed when the store is created and recorded in `manifest.json`, so later opens need no flag:

```bash verify
relate --database ./team-json --read-only --mode envelope "MATCH (p:Person) RETURN p.name AS name ORDER BY name"
```

```text
{"columnTypes":["STRING"],"columns":["name"],"notifications":[],"planCache":"miss","rows":[{"name":"Ada"},{"name":"Grace"},{"name":"Noor"}],"timings":{"elapsed_ms":0.536292,"execute_ms":0.376833,"lex_parse_ms":0.029833000000000002,"lower_ms":0.038042,"parse_ms":0.10087499999999999,"plan_ms":0.050334000000000004,"result_generation_ms":0.00020800000000000001,"rewrite_ms":0.008},"truncated":false}
```

`RELATE_SNAPSHOT_FORMAT=json` makes the same choice for a new store, including from the clients. A third codec, `markdown`, writes one note per node for a folder people edit by hand; the [formats reference](https://relatedb.ai/docs/reference/storage/formats/) describes it.

## Open a store from Python or Node.js

The clients take the same three spellings: `":memory:"`, a directory path, or a path ending in `.rl`. Each opens writable by default and creates a missing store. In Python, `relate.connect(path)` opens one, as in the [Python tutorial](https://relatedb.ai/docs/get-started/python/), and `read_only=True` opens it read-only. This program writes the graph into each layout, then opens the `.rl` file [read-only](https://relatedb.ai/docs/reference/glossary/#read-only):

```python run name=open-layouts title="layouts.py"
import relate

seed = """
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)
"""

for path in [":memory:", "./team", "./team.rl"]:
    conn = relate.connect(path)
    conn.execute(seed)
    people = conn.execute("MATCH (p:Person) RETURN count(p) AS people").fetchone()["people"]
    print(f"{path}: {people} people")
    conn.close()

reader = relate.connect("./team.rl", read_only=True)
rows = reader.execute("MATCH (p:Person) RETURN p.name AS name ORDER BY name").fetchall()
print("read-only:", [row["name"] for row in rows])
reader.close()
```

```text
:memory:: 3 people
./team: 3 people
./team.rl: 3 people
read-only: ['Ada', 'Grace', 'Noor']
```

In Node.js, `Database.open` takes the same paths, and `{ readOnly: true }` opens a store read-only. The Node.js handles in this program stay open until it exits, so the read-only open goes to the directory store, which allows a reader beside its writer:

```js run name=open-layouts-js title="layouts.cjs"
const { Database } = require("@relate/relate");

const seed = `
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)`;

for (const path of [":memory:", "./team", "./team.rl"]) {
  const db = Database.open(path);
  const conn = db.connect();
  conn.query(seed);
  const people = conn.query("MATCH (p:Person) RETURN count(p) AS people").rows[0].people;
  console.log(`${path}: ${people} people`);
}

const reader = Database.open("./team", { readOnly: true }).connect();
const names = reader.query("MATCH (p:Person) RETURN p.name AS name ORDER BY name").rows;
console.log("read-only:", names.map((row) => row.name));
```

```text
:memory:: 3 people
./team: 3 people
./team.rl: 3 people
read-only: [ 'Ada', 'Grace', 'Noor' ]
```

## Switch layouts later

`relate clone` copies a store into a new store of either layout, and `--to-snapshot-format` changes the codec on the way. The source is opened read-only and never written; the destination must not exist yet. Turn the `.rl` file into a directory store:

```bash verify
relate clone ./team.rl ./team-copy
```

Then make a JSON copy of that:

```bash verify
relate clone --to-snapshot-format json ./team-copy ./team-copy-json
```

Switch when the job changes:

| If                                          | Move to                               |
| ------------------------------------------- | ------------------------------------- |
| A second process must read while one writes | A directory store                     |
| The store is shipped, attached, or archived | A `.rl` file                          |
| Reviewers diff the graph in pull requests   | A directory store with the JSON codec |
| The `.rl` file is larger than its content   | A fresh `.rl` from `relate clone`     |

## Verify

`relate info` names the layout and codec of a store. The JSON copy reports both on its first two lines:

```bash verify
relate info --database ./team-copy-json | head -n 2
```

```text
layout: native directory
codec: json
```

## If it goes wrong

- **`storage error: snapshot codec mismatch: directory is json, requested parquet`** (exit code 1). `--snapshot-format` named a codec other than the one the directory was created with. Asking for Parquet on the JSON store fails:

  ```bash verify
  relate --database ./team-json --snapshot-format parquet --read-only --mode envelope "RETURN 1 AS ok"
  ```

  ```text
  Failed to open Relate: storage error: snapshot codec mismatch: directory is json, requested parquet
  {"error":{"code":"fatal_error","exit_code":1,"kind":"storage","message":"storage error: snapshot codec mismatch: directory is json, requested parquet","retryable":false}}
  ```

  Drop the flag, or make a copy in another codec with `relate clone --to-snapshot-format <codec> <source> <dest>`.

- **`storage locked: <path>`** (exit code 5). Another process holds the store. A `.rl` file can't be opened at all while a writer holds it, even `--read-only`. If a reader has to run beside the writer, move to a directory store with `relate clone`. The [locked error](https://relatedb.ai/docs/reference/errors/#cli-conflict-locked) has the details.

- **`Failed to clone database: storage error: clone destination already exists`** (exit code 1). `relate clone` never overwrites. Pick a new destination path.

## Related

- [Storage formats](https://relatedb.ai/docs/reference/storage/formats/): the files each layout and codec writes, versions, and measured sizes.
- [Back up and restore a store](https://relatedb.ai/docs/guides/storage/backup-and-restore/): `relate clone` as the backup command.
- [Storage and durability](https://relatedb.ai/docs/concepts/storage-and-durability/): the write path every layout shares.
- [Share one store between two processes](https://relatedb.ai/docs/guides/operate/two-processes-one-store/): a writer and readers on one directory store.

## Terms used on this page

- **store**: A relate database: a directory, a single `.rl` file, or `:memory:`, opened with `--database`.
- **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`.
- **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.
- **codec**: The on-disk encoding of a store's snapshot: Parquet (the default), JSON or markdown, fixed when the store is created.
- **read-only**: An open mode (`--read-only`) that never writes, checkpoints or migrates the store; writes fail, and a directory store can be read beside its writer.
