Skip to content

Add continuous SQLite replica via Online Backup API - #1105

Open
vincenzopalazzo wants to merge 1 commit into
lightningdevkit:mainfrom
vincenzopalazzo:feat/sqlite-continuous-backup
Open

vincenzopalazzo wants to merge 1 commit into
lightningdevkit:mainfrom
vincenzopalazzo:feat/sqlite-continuous-backup

Conversation

@vincenzopalazzo

@vincenzopalazzo vincenzopalazzo commented Sep 17, 2026

Copy link
Copy Markdown
Member

Motivation

Operators who run a self-custodial LDK Node need a second copy of {storage_dir}/ldk_node_data.sqlite that stays current as the node persists. Channel monitors, payments, and the BDK wallet changeset all live in that file; losing it (disk failure, bad deploy, accidental wipe) is unrecoverable without a replica.

Core Lightning already solves this for its wallet DB with --wallet=sqlite3://primary:backup. Every wallet write is mirrored to a second SQLite file via SQLite's Online Backup API. A replica error is a wallet error, so the backup cannot silently lag.

This PR brings the same model to LDK Node's SQLite store.

Builder::set_storage_dir_path is a directory, not a SQLite URI, and : is a valid Unix path character, so we do not parse primary:backup out of the storage path. The replica is a separate setter:

builder.set_storage_dir_path("/home/user/.ldk".into());
builder.set_sqlite_backup_path("/my/backup/ldk_node_data.sqlite".into());

Behaviour

  • After opening the primary DB (including schema setup/migration) and after every successful KVStore::write / KVStore::remove, the store copies the whole primary database to backup_path with Connection::backup.
  • A failed replica update fails the persist; LDK retries as it would for any other persist error.
  • Reads are unchanged.
  • Restore: place the replica at {storage_dir}/ldk_node_data.sqlite and build() as usual.
  • The replica is a hot-spare file, not a second live node. Seed/entropy is not included (it lives in a separate file, same as today).

This is the same two-step as CLN (commit, then backup). A crash between the primary commit and the replica copy can leave the backup one persist behind until the next successful start/persist.

The replica is a consistent full copy of the primary (schema, indexes, user_version), not a second connection replaying SQL. Cost is O(database size) per persist.

API

  • Rust: Builder::set_sqlite_backup_path
  • UniFFI (Swift/Kotlin/Python): same setter, gated on storage-sqlite
  • SqliteStore::new_with_backup for direct store use

Benchmarks

Measured locally against the KV hot path from #915 (cargo bench --bench database --features bench -- payment_store_single), comparing stock SQLite to the same store with a continuous replica. Apple Silicon, Criterion 20 samples / 5s measurement.

op sqlite sqlite + backup ratio
write_new_key 270 µs 764 µs 2.8×
write_existing_key 15 µs 262 µs 17×
read_existing_key 13.7 µs 13.7 µs 1.0×
remove_existing_key 231 µs 489 µs 2.1×

The 17× is 15 µs vs 15 µs + copy every page. Absolute overhead here is ~250 µs on an empty store; it grows with database size. Reads are free.

The #915 bench overlay used for this measurement is not in this PR.

Testing

cargo test --lib io::sqlite_store — 17 passed, including write + remove replication and rejecting a backup path that coincides with the primary file.

AI assistance: Goose (AAIF).

Mirror `{storage_dir}/ldk_node_data.sqlite` to a second file after open
and after every successful KV write/remove, Core Lightning-style.
Replica failures fail the persist. Restore by placing the replica at
`{storage_dir}/ldk_node_data.sqlite`. Seed/entropy is not included.

AI assistance: Goose (AAIF).
@ldk-reviews-bot

ldk-reviews-bot commented Sep 17, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@vincenzopalazzo
vincenzopalazzo force-pushed the feat/sqlite-continuous-backup branch from 865aecc to 2f0a93e Compare September 18, 2026 02:11

@tnull tnull left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a feature we want and have explored quite a bit already.

We should however generally offer the option for a local SQLite-based backup, not only when SQLite is set as the storage backend. It's particular important for any kind of remote storage where you don't fully trust that the remote won't go away (e.g. VSS).

But, please see the parallel ongoing work at #692 etc.

@enigbe

enigbe commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

There's ongoing work regarding backup and resilvering here (#1073), that's linked to #692 as Elias mentioned. Happy to get a review and discuss shared strategies. Taking a look at this now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants