Backups

The backups plugin copies committed transactions to local folders and S3-compatible storage. It can restore a missing or older database from a backup target.

Enable backups in the plugins option and provide its settings when you open the database. An absent plugin selection enables no plugins. Release packages include backups as a plugin library and in the static archive. The core library does not contain it.

The history and backups plugins cannot share a database. History retention changes the transaction history that backups must preserve.

Settings

TRON734 B
Offset (hex)Bytes (hex)Meaning
00054 52 4F 4Emagic TRON
0044C 70617468txt "path"
009CC 6261636B757073 2F6D61696Etxt "backups/main"
0160F 0A 04000000 0900 0000map leaf, size 10: @4 → @9
0204C 66696C65txt "file"
0250F 0A 20000000 1600 0000map leaf, size 10: @20 → @16
02F6C 6275636B6574txt "bucket"
036CC 7465616D2D6261 636B757073txt "team-backups"
0430F 0A 2F000000 3600 0000map leaf, size 10: @2F → @36
04D6C 707265666978txt "prefix"
0544C 6D61696Etxt "main"
0590F 0A 4D000000 5400 0000map leaf, size 10: @4D → @54
06314 12 736563726574 4163636573734B65 79456E76txt "secretAccessKeyEnv"
077DC 4241434B55505F 534543524554txt "BACKUP_SECRET"
0850F 0A 63000000 7700 0000map leaf, size 10: @63 → @77
08FEC 6163636573734B 65794964456E76txt "accessKeyIdEnv"
09EDC 4241434B55505F 4B45595F4944txt "BACKUP_KEY_ID"
0AC0F 0A 8F000000 9E00 0000map leaf, size 10: @8F → @9E
0B68C 656E64706F696E 74txt "endpoint"
0BF14 25 68747470733A 2F2F73332E65752D 63656E7472616C2D 312E616D617A6F6E 6177732E636F6Dtxt "https://s3.eu-central-1.…"
0E60F 0A B6000000 BF00 0000map leaf, size 10: @B6 → @BF
0F06C 726567696F6Etxt "region"
0F7CC 65752D63656E74 72616C2D31txt "eu-central-1"
1040F 0A F0000000 F700 0000map leaf, size 10: @F0 → @F7
10E07 0E 05000000 E600 0000 04010000map branch, size 14, depth 1, slots 0, 2: @E6 → @104
11C07 1A B4040000 4300 0000 59000000 8500 0000 AC000000 0E01 0000map branch, size 26, depth 0 (root), slots 2, 4, 5, 7, 10: @43 → @59 → @85 → @AC → @10E
1369C 616C676F726974 686Dtxt "algorithm"
1404C 7A737464txt "zstd"
1450F 0A 36010000 4001 0000map leaf, size 10: @136 → @140
14F5C 6C6576656Ctxt "level"
15502 03000000000000 00i64 3
15E0F 0A 4F010000 5501 0000map leaf, size 10: @14F → @155
Show all 65 nodes
Offset (hex)Bytes (hex)Meaning
16807 0E 40020000 4501 0000 5E010000map branch, size 14, depth 0 (root), slots 6, 9: @145 → @15E
1764C 23647572txt "#dur"
17B4C 5054354Dtxt "PT5M"
1800F 0A 76010000 7B01 0000map leaf, size 10: @176 → @17B
18ACC 7472616E736163 74696F6E73txt "transactions"
19702 E8030000000000 00i64 1000
1A00F 0A 8A010000 9701 0000map leaf, size 10: @18A → @197
1AA8C 6475726174696F 6Etxt "duration"
1B30F 0A AA010000 8001 0000map leaf, size 10: @1AA → @180
1BD07 0E 80800000 A001 0000 B3010000map branch, size 14, depth 0 (root), slots 7, 15: @1A0 → @1B3
1CB14 10 656E63727970 74696F6E4B657945 6E76txt "encryptionKeyEnv"
1DD14 15 4241434B5550 5F454E4352595054 494F4E5F4B4559txt "BACKUP_ENCRYPTION_KEY"
1F40F 0A CB010000 DD01 0000map leaf, size 10: @1CB → @1DD
1FEAC 6D6178696D756D 4C6167txt "maximumLag"
2090F 0A FE010000 BD01 0000map leaf, size 10: @1FE → @1BD
2132C 7333txt "s3"
2160F 0A 13020000 1C01 0000map leaf, size 10: @213 → @11C
220BC 636F6D70726573 73696F6Etxt "compression"
22C0F 0A 20020000 6801 0000map leaf, size 10: @220 → @168
23607 16 00550000 F401 0000 09020000 1602 0000 2C020000map branch, size 22, depth 0 (root), slots 8, 10, 12, 14: @1F4 → @209 → @216 → @22C
24C5C 6C6F63616Ctxt "local"
2520F 0A 4C020000 2500 0000map leaf, size 10: @24C → @25
25C7C 6F666673697465txt "offsite"
2640F 0A 5C020000 3602 0000map leaf, size 10: @25C → @236
26E07 0E 08400000 5202 0000 64020000map branch, size 14, depth 0 (root), slots 3, 14: @252 → @264
27C14 11 627970617373 556E726561636861 626C65txt "bypassUnreachable"
28F01bit false
2900F 0A 7C020000 8F02 0000map leaf, size 10: @27C → @28F
29A7C 74617267657473txt "targets"
2A20F 0A 9A020000 6E02 0000map leaf, size 10: @29A → @26E
2AC07 0E 02200000 9002 0000 A2020000map branch, size 14, depth 1, slots 1, 13: @290 → @2A2
2BA07 0A 10000000 AC02 0000map branch, size 10, depth 0 (root), slots 4: @2AC
2C47C 6261636B757073txt "backups"
2CC0F 0A C4020000 BA02 0000map leaf, size 10: @2C4 → @2BA
Offset (hex)Bytes (hex)Meaning
SettingValueEffect
targetsObjectEach member defines a named target.
bypassUnreachableBoolean, default falsePermit writes during a target outage. Lag limits and recovery checks still apply.

Each target requires exactly one storage member: file or s3. These members are optional:

MemberValueEffect
compressionObjectSelect algorithm: none or zstd. Zstandard accepts an optional integer level. Compression applies to transaction runs.
encryptionKeyEnvTextName an environment variable containing a 64-digit hexadecimal AES-256 key. Runs, payload chunks and payload manifests use AES-256-GCM encryption.
maximumLagObjectRefuse further data writes when the target exceeds any specified lag limit.
Lag limitValueMeasurement
transactionsPositive integerTransactions the target has not confirmed
logicalBytesPositive integerStored transaction-row bytes the target has not confirmed
binaryBytesPositive integerPayload bytes the target has not confirmed
durationPositive #durAge of the oldest unconfirmed transaction

The file member requires path. A relative path resolves against the working directory when the database opens.

S3 memberRequiredValue
endpointYesS3 service URL
regionYesSigning region
bucketYesBucket name
accessKeyIdEnvYesEnvironment variable containing the access key ID
secretAccessKeyEnvYesEnvironment variable containing the secret access key
prefixNoObject key prefix
sessionTokenEnvNoEnvironment variable containing the session token
addressingStyleNopath or virtual

On Linux and macOS, S3 requires libcurl 7.84.0 or newer with thread-safe initialization and asynchronous DNS. The plugin loads libcurl on the first S3 connection. Windows uses WinHTTP. If libcurl is unavailable, S3 targets report Backup_Operating_System. Local file targets remain usable.

Unknown members, invalid values and duplicate storage locations make the database open fail. The plugin reads secret values from the environment when needed. It does not store them in the database.

Stored options

Startup settings are authoritative on every open. The plugin records them as read-only definitions of the backup type:

DefinitionContents
backups/targets/<name>Settings for the named target
backups/settingsPlugin-wide settings

Different startup settings replace these definitions in one transaction. Definitions for removed targets are deleted. Identical settings produce no transaction.

Attempts to write, edit, move or delete these definitions fail with definition_read_only. To change the settings, close the database and open it with the new settings.

Format

Each target stores immutable runs and a mutable head file, manifest.json. A run contains one JSON line per transaction. Each line is exactly the transaction's merge patch. It includes the transaction ID, stardust/committedAt and stardust/transaction.

A SHA-256 chain links the transaction lines. The head identifies the runs, newest transaction and chain digest. The plugin merges small runs into larger runs to limit the number of stored files.

Open and recovery

On open, the plugin compares the local history with each readable target. It selects the newest history only if every other compared history is an exact prefix of it. Missing local transactions are restored with their original transaction IDs, commit times, entity IDs and field names. Older targets receive the transactions they lack.

Data writes remain held until recovery and target catch-up finish. This also applies with bypassUnreachable true. A source target reports restoring while the database restores from it. A source failure holds writes until recovery can continue. After a crash or database close, the next open continues after the last restored transaction. Reactors exclude restored transactions because the history already contains their live-mutation results.

These conditions make the open fail without changing the database:

ConditionStatusCode
Histories diverge after a common transactionconflictbackup_history_divergent
The newest target cannot supply history needed to compare another copyunavailablebackup_target_unreadable
The database is new or empty and every target is unreachableunavailablebackup_targets_unreachable
Recovery needs object payloads but objects is disabledunavailablebackup_objects_required

Error messages name the affected targets. Divergence errors use local for the database. Unreachable storage includes network or operating-system failures and a target held by another database. A new database cannot safely start a history while unreachable targets might contain an existing one.

A missing storage location or unavailable encryption key does not itself make the open fail. The target reports the failure in its status. The plugin retries targets it cannot compare during open. A later divergence reports Backup_History_Divergent in the target status.

After an empty open or a restore, an unexamined target might contain newer history. It reports comparisonPending, and data writes remain held until comparison and any further recovery finish. bypassUnreachable true does not bypass this check.

Write admission

Initial comparison and recovery hold data writes and the stored-options transaction. After recovery, a target that exceeds its maximumLag holds further data writes. A held data write returns conflict with code write_held. Retry the write later, or call sync first. Maintenance, restore transactions and plugin progress records remain permitted.

An unreachable target or Backup_Memory_Exhausted failure triggers retries with increasing delays, up to five minutes. bypassUnreachable true permits writes during retries except when recovery or comparison requires that target. Lag limits still apply. Each commit immediately increases the measured lag. A target within its limits permits the next write.

Runtime status

Call status to inspect backup progress without contacting a target.

FieldContents
enabledWhether backups are enabled
headNewest committed transaction
writesAdmittedWhether data writes are permitted
optionsStoredWhether stored definitions match startup settings
optionsCodeLast stored-options commit failure, or ""
targetsPer-target status entries
Target fieldContents
nameTarget name
phaseCurrent phase, listed below
cursorNewest confirmed transaction, or null
synchronizedWhether the target has confirmed the current history
restoringWhether the database is restoring history from this target
comparisonPendingWhether comparison is still required after an open
codeLast failure, or ""
lagCurrent transactions, logicalBytes and binaryBytes, when measured
PhaseMeaning
startingOpening the target
validatingReading and checking stored history
reconcilingRestoring or comparing transactions
synchronizingStoring new transactions
synchronizedAll committed transactions confirmed
retryingWaiting to retry after a failure
failedStopped until the database opens again
stoppedThe database is closing

When to call sync

Backups run automatically after commits. A successful write can return before a target stores the transaction. Call sync when an operation requires every target to confirm the current history, such as a recovery test.

Sync waits for the transaction current at the call, or the stored-options transaction if that commits later. It succeeds after the options are stored and every target confirms that transaction. Targets with maximumLag must also report current lag measurements. A failed target or refused stored-options commit makes sync report failed. Use maxWait to set the wait duration. Omitting it, or using zero, checks once without waiting.

Object payloads

The objects plugin identifies file payloads through stardust/object/digest. Backups store referenced payloads before the transaction run that names them:

LocationContents
binaries/chunks/Shared payload chunks
binaries/manifests/Payload manifests

A shared chunk is stored once. Targets retain stored payloads permanently. encryptionKeyEnv encrypts chunks and manifests. Payloads are not compressed.

Recovery copies missing payloads into the object store. Before each referencing transaction commits, recovery checks every chunk and the whole payload against their digests. These checks also apply to payloads already present locally. Each payload digest is published with its referencing transaction.

A missing or corrupt payload stops recovery before that transaction and reports Backup_Corrupted. Earlier restored transactions remain committed, and data writes remain held.

Recovery of payload references requires objects. If this requirement is discovered during open, it fails with backup_objects_required, as listed under open and recovery. A target examined later reports Backup_Objects_Required before restoring any transactions. The same status applies when a target must store payloads that the database cannot read. Each pending payload contributes its size to the binaryBytes lag.

Operations

OperationReturnsSummary
backups-statusdocumentbackups.status() returns an owned document describing backup progress.
backups-syncnonebackups.sync(options) waits for every target to confirm the current transaction history.

backups-status

clojure4 B
[db]

Parameters

ParameterTypeOwnershipDefaultMeaning
dbBackupsBorrowed for the callRequiredThe backups owner for this operation. Keep it alive during the call.

Errors

StatusWhen
:unavailablethe database does not enable the backups plugin
:closedthe database is closed
:out-of-memorynative memory is exhausted

Notes

See runtime status for the result fields. This call does not contact targets.

Examples

Describe the backup runtime

clojure22 B
(sd/backups-status db)
TRON226 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
046C 737461747573txt "status"
0BBC 756E617661696C 61626C65txt "unavailable"
170F 0A 04000000 0B00 0000map leaf, size 10: @4 → @B
216C 656E74697479txt "entity"
2800nil
290F 0A 21000000 2800 0000map leaf, size 10: @21 → @28
337C 6D657373616765txt "message"
3B14 19 74686520706C 7567696E20697320 6E6F7420656E6162 6C6564txt "the plugin is not enable…"
560F 0A 33000000 3B00 0000map leaf, size 10: @33 → @3B
6007 0E 08020000 2900 0000 56000000map branch, size 14, depth 1, slots 3, 9: @29 → @56
6E4C 70617468txt "path"
7300nil
740F 0A 6E000000 7300 0000map leaf, size 10: @6E → @73
7E5C 6669656C64txt "field"
8400nil
850F 0A 7E000000 8400 0000map leaf, size 10: @7E → @84
8F4C 636F6465txt "code"
94BC 756E617661696C 61626C65txt "unavailable"
A00F 0A 8F000000 9400 0000map leaf, size 10: @8F → @94
AA6C 6F6666736574txt "offset"
B100nil
B20F 0A AA000000 B100 0000map leaf, size 10: @AA → @B1
BC07 1E 51620000 1700 0000 60000000 7400 0000 85000000 A000 0000 B2000000map branch, size 30, depth 0 (root), slots 0, 4, 6, 9, 13, 14: @17 → @60 → @74 → @85 → @A0 → @B2

C exports: stardust_db_plugin_call, stardust_result_take_document, stardust_result_error, stardust_result_release

backups-sync

clojure4 B
[db]
clojure12 B
[db options]

Parameters

ParameterTypeOwnershipDefaultMeaning
dbBackupsBorrowed for the callRequiredThe backups owner for this operation. Keep it alive during the call.
optionsDocumentBorrowed for the callAbsent selects operation defaultsThe borrowed call options, or none. Optional maxWait is a nonnegative native duration, such as {maxWait {#dur PT5S}}. Without it, or with zero, the call checks once.

Errors

StatusWhen
:invalid-argumentoptions is not an object, or an option is invalid, duplicated or unsupported
:failedthe plugin's work failed; status describes the failure
:timeoutmaxWait passed before every target confirmed
:unavailablethe database does not enable the backups plugin
:closedthe database is closed

Notes

Database close interrupts an active wait. See when to call sync.

Examples

Wait until every target stored the history

clojure103 B
(with-open [sync-options (sd/parse "{maxWait {#dur PT5S}}" :dust)]
  (sd/backups-sync db sync-options))
TRON226 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
046C 737461747573txt "status"
0BBC 756E617661696C 61626C65txt "unavailable"
170F 0A 04000000 0B00 0000map leaf, size 10: @4 → @B
216C 656E74697479txt "entity"
2800nil
290F 0A 21000000 2800 0000map leaf, size 10: @21 → @28
337C 6D657373616765txt "message"
3B14 19 74686520706C 7567696E20697320 6E6F7420656E6162 6C6564txt "the plugin is not enable…"
560F 0A 33000000 3B00 0000map leaf, size 10: @33 → @3B
6007 0E 08020000 2900 0000 56000000map branch, size 14, depth 1, slots 3, 9: @29 → @56
6E4C 70617468txt "path"
7300nil
740F 0A 6E000000 7300 0000map leaf, size 10: @6E → @73
7E5C 6669656C64txt "field"
8400nil
850F 0A 7E000000 8400 0000map leaf, size 10: @7E → @84
8F4C 636F6465txt "code"
94BC 756E617661696C 61626C65txt "unavailable"
A00F 0A 8F000000 9400 0000map leaf, size 10: @8F → @94
AA6C 6F6666736574txt "offset"
B100nil
B20F 0A AA000000 B100 0000map leaf, size 10: @AA → @B1
BC07 1E 51620000 1700 0000 60000000 7400 0000 85000000 A000 0000 B2000000map branch, size 30, depth 0 (root), slots 0, 4, 6, 9, 13, 14: @17 → @60 → @74 → @85 → @A0 → @B2

C exports: stardust_db_plugin_call, stardust_result_take_document, stardust_result_error, stardust_result_release