GQLDB takes hot backups — the database keeps serving reads and writes throughout. A backup is a single compressed tar archive containing every SST file for the target scope plus a backup_meta.json header (WAL sequence, graph name, node/edge counts, timestamp). Both per-graph and whole-database backups are supported.
Two API surfaces are available; pick whichever fits your workflow:
| Surface | When to use |
|---|---|
Statement form (BACKUP …, RESTORE …) | Ad-hoc operator commands, schema-style scripting, anywhere you'd otherwise type GQL. Has the full option surface (incremental, scope, overwrite, verify). |
Function form (db.backup(), db.restore(), db.backups()) | Driver code, automation, composition with RETURN pipelines. Subset of the statement form. |
Both write the same on-disk format and use the same internal code path — interchangeable for a basic full backup.
Use BACKUP GRAPH to create a backup of a single graph as a compressed archive.
Syntax<backup graph statement> ::= "BACKUP GRAPH" [ <graph name>] "TO" <directory and prefix string> <directory and prefix> ::= <directory> "/" <prefix>
Details
<graph name> is optional. If omitted, backs up the current graph.<directory> is the absolute path where the backup file is created on the database host. The directory must already exist.<prefix>.gqlbackup.tar.gz.Back up the current graph to /data/backups/, creating the file default.gqlbackup.tar.gz:
GQLBACKUP GRAPH TO "/data/backups/default"
Back up a specific graph without switching to it:
GQLBACKUP GRAPH myGraph TO "/data/backups/myGraph"
Returns a single row:
| Field | Description |
|---|---|
status | success on completion. |
type | full or incremental. |
graph_name | The graph that was backed up. |
path | Full path to the backup file (with extension). |
node_count | Number of nodes in the backup. |
edge_count | Number of edges in the backup. |
size | Backup file size in bytes. |
wal_sequence | WAL sequence number at backup time. Used by RESTORE to know where to resume. |
duration_ms | Backup duration in milliseconds. |
timestamp | When the backup was created. |
The backup is hot. The server flushes outstanding writes to disk first (so the archive contains a consistent SST set), then copies SSTs to the archive. New writes that arrive during the copy land in the next WAL window — they are not in this backup file. The wal_sequence field tells you the exact cutoff.
Use RESTORE GRAPH to restore a graph from a backup archive.
Syntax<restore graph statement> ::= "RESTORE GRAPH FROM" <filepath string> [ "OVERWRITE" ]
Details
<filepath string> is the full path to the backup file, including the .gqlbackup.tar.gz extension.OVERWRITE is optional. With it, the existing graph is dropped atomically and rebuilt from the archive. Without it, the command fails if the target graph already exists.Restore a graph from a backup:
GQLRESTORE GRAPH FROM "data/backups/my_backup.gqlbackup.tar.gz"
Restore and overwrite an existing graph:
GQLRESTORE GRAPH FROM "data/backups/my_backup.gqlbackup.tar.gz" OVERWRITE
Returns a single row:
| Field | Description |
|---|---|
status | success on completion. |
graph_name | The graph that was restored. |
type | full or incremental. |
node_count | Number of nodes restored. |
edge_count | Number of edges restored. |
duration_ms | Restore duration in milliseconds. |
timestamp | When the restore completed. |
Any sessions bound to the graph via USE GRAPH see the cutover on their next statement.
Incremental backups capture only the SST files changed since a base (full) backup. They are smaller and faster to create but require the base backup to be available during restoration.
First, create a full backup, then reference it as the base:
GQL-- Step 1: full backup BACKUP GRAPH TO "data/backups/full" -- Step 2: data changes INSERT (:Person {name: "NewUser"}) -- Step 3: incremental backup referencing the full BACKUP GRAPH TO "data/backups/incr" INCREMENTAL BASE "data/backups/full.gqlbackup.tar.gz"
Incremental backup files use the extension .gqlbackup.inc.tar.gz.
Restore the full backup first, then apply the incremental on top:
GQL-- Step 1: restore the full RESTORE GRAPH FROM "data/backups/full.gqlbackup.tar.gz" OVERWRITE -- Step 2: restore the incremental RESTORE GRAPH FROM "data/backups/incr.gqlbackup.inc.tar.gz" OVERWRITE
Missing an incremental in the chain is a fatal error at restore time. A weekly full + daily incremental pattern is the common scheduling shape — small nightly footprint, recovery never further than one full away.
Use BACKUP DATABASE to back up all graphs at once. The system graph (__system__, holding RBAC users / roles) is included.
Syntax<backup database statement> ::= "BACKUP DATABASE TO" <directory string>
Details
<directory string> is the absolute path on the database host. The directory is created automatically if it doesn't exist.GQLBACKUP DATABASE TO "data/backups/db_snapshot"
This creates a directory with one archive per graph plus database-level metadata:
File Structuredata/backups/db_snapshot/ ├── <graph1>.gqlbackup.tar.gz -- per-graph archive ├── <graph2>.gqlbackup.tar.gz ├── meta.json -- global database metadata └── db_backup_meta.json -- database backup manifest
Returns a single row:
| Field | Description |
|---|---|
status | success on completion. |
graph_count | Number of graphs included. |
total_size | Combined size of all archives in bytes. |
duration_ms | Backup duration in milliseconds. |
timestamp | When the backup was created. |
path | The target directory. |
A whole-database backup is the standard before a major-version upgrade or before a destructive operation like TRUNCATE GRAPH.
<restore database statement> ::=
"RESTORE DATABASE FROM" <directory string> [ "OVERWRITE" ]
Details
<directory string> is the absolute path of the database backup directory.OVERWRITE replaces all graphs in the running database with the backup's contents. Without it, only graphs not currently present are restored — useful for partial recovery.GQLRESTORE DATABASE FROM "data/backups/db_snapshot" OVERWRITE
Returns a single row:
| Field | Description |
|---|---|
status | success on completion. |
graph_count | Number of graphs restored. |
duration_ms | Restore duration in milliseconds. |
timestamp | When the restore completed. |
The system graph is restored atomically with the user graphs, so RBAC state (users, roles, grants) returns to its snapshot moment.
GQLSHOW BACKUPS
With no arguments, reads from the internal backup catalog — the database's own record of every successful and failed backup operation. Each row carries an id you can pass to DROP BACKUP.
| Field | Description |
|---|---|
id | Unique backup ID. |
type | full or incremental. |
scope | graph or database. |
graph_name | The source graph name. |
path | Backup file path. |
node_count, edge_count | Counts in the backup. |
size | Backup file size in bytes. |
wal_sequence | WAL sequence number at backup time. |
duration_ms | Backup duration in milliseconds. |
status | completed or failed. |
available | Live check that the file at path still exists. Useful for detecting tarballs moved or deleted out of band. |
timestamp | When the backup was created. |
Filter by graph name:
GQLSHOW BACKUPS FOR GRAPH myGraph
Scan a directory on disk instead of the catalog — re-reads each archive's backup_meta.json header:
GQLSHOW BACKUPS FROM "/backups/directory" SHOW BACKUPS FROM "/backups/directory" FOR GRAPH myGraph
Directory-scan form returns a slightly different set (no id / scope / status, plus compressed):
| Field | Description |
|---|---|
type, graph_name, path | As above. |
node_count, edge_count, size, wal_sequence | As above. |
compressed | Whether the backup is gzip-compressed. |
timestamp | When the backup was created. |
Directory scan is slower than the catalog but is the truth-source if the catalog and disk have diverged.
Retrieve metadata for a specific backup file:
GQLSHOW BACKUP "data/backups/my_backup.gqlbackup.tar.gz"
Returns the same columns as the directory-scan form above, for one file.
Check that a backup archive is structurally valid before relying on it:
GQLVERIFY BACKUP "/backups/my_backup.gqlbackup.tar.gz"
Returns:
| Field | Description |
|---|---|
valid | Whether the backup is structurally valid (true or false). |
graph_name | The source graph name. |
file_count | Number of files in the backup archive. |
total_size | Total uncompressed size in bytes. |
wal_sequence | WAL sequence number at backup time. |
timestamp | When the backup was created. |
errors | Validation errors, if any (empty when valid = true). |
VERIFY BACKUP only checks structure (tar headers parse, backup_meta.json is present and well-formed, SST headers look sane). It does not restore-and-replay — pair it with periodic test restores into a throwaway -db directory for body-integrity confidence.
GQLDROP BACKUP "<id>" DROP BACKUP "<id>" IF EXISTS
Removes the row from the catalog and best-effort deletes the underlying tar.gz. If another catalog row references the same path (e.g., a renamed copy), the file is kept and file_deleted = false is returned. IF EXISTS turns "id not found" from an error into an empty result.
For driver code and automation where you'd rather not parse statement results:
GQLRETURN db.backup("data/backups/myGraph") RETURN db.backup("data/backups/myGraph", {compress: true}) RETURN db.backup("data/backups/myGraph", {incremental: true}) RETURN db.restore("data/backups/myGraph.gqlbackup.tar.gz") RETURN db.restore("data/backups/myGraph.gqlbackup.tar.gz", {overwrite: true}) RETURN db.backups("data/backups")
| Function | Returns | Notes |
|---|---|---|
db.backup(path, opts?) | Map | Backs up the current graph. opts.compress (Bool, default true), opts.incremental (Bool, default false). No BASE argument for incremental in the function form — use the statement. |
db.restore(path, opts?) | Map | opts.overwrite (Bool, default false). |
db.backups(dirPath) | List | Directory scan, equivalent to SHOW BACKUPS FROM "<dir>". |
Return maps for db.backup() and db.restore():
db.backup() → status, path, type, graphName, nodeCount, edgeCount, size, walSequence, timestamp, compressed.db.restore() → status, graphName, nodeCount, edgeCount, type, timestamp.db.backups() → list of maps with path, type, graphName, nodeCount, edgeCount, size, walSequence, timestamp, compressed.The function form skips per-graph / whole-database scope distinctions and INCREMENTAL BASE available in the statement form. Reach for it when you're already in a fluent RETURN-style API and don't need the extras.
A full backup archives the entire graph directory, so all on-disk state is preserved:
After restore, in-memory managers and caches are reinitialized from the restored on-disk files:
_id is enabled) is rebuilt from the restored data.The following are not in the backup:
ALTER GRAPH <name> SET COMPUTE ENABLED if needed.backup_catalog.json) itself. The catalog is per-database, not per-graph.| Statement / function | Required RBAC operation |
|---|---|
BACKUP GRAPH, BACKUP DATABASE, db.backup(), DROP BACKUP | OpBackup |
RESTORE GRAPH, RESTORE DATABASE, db.restore() | OpRestore |
SHOW BACKUPS, SHOW BACKUP, VERIFY BACKUP, db.backups() | OpShowSchema |
Grant via RBAC; see Access Control for role wiring.
| Type | Extension |
|---|---|
| Full graph backup | .gqlbackup.tar.gz |
| Incremental backup | .gqlbackup.inc.tar.gz |
NOTEDo not include the extension when specifying the destination path in
BACKUP GRAPHordb.backup()— it is added automatically.
Internal format details:
| Aspect | Detail |
|---|---|
| Container | tar.gz (gzip by default; opts.compress = false produces a plain tar). |
| First entry | backup_meta.json — backup format version, GQLDB version, type (full/incremental), scope (graph/database), graph name, WAL sequence, node/edge counts, optional base_backup path for incrementals. |
| Body | The SST files and metadata sidecars for the source graph. |
The format is forward-compatible across patch releases. Across minor versions, a backup taken on 1.x restores cleanly on 1.y; the engine may run a one-shot migration on first read of older SSTs (transparent to callers — see Installation → Updating).
BACKUP DATABASE immediately before a server upgrade. Restoring is the cleanest rollback path if the new version misbehaves.VERIFY BACKUP confirms each archive's header parses. Pair it with periodic test restores into a throwaway -db directory to confirm the body is intact, not just the header.SHOW BACKUPS and available. Catalog-form SHOW BACKUPS carries an available column that re-checks whether the file at path still exists. Treat it as the periodic reality check that your retention script isn't deleting files out from under the catalog.OVERWRITE replaces the entire graph with the backup contents. Without it, the restore fails if the target graph already exists.TRUNCATE, or operator error. Always keep offline backups even in a 3-node cluster. See Clustering.