Skip to content

migratekvdb: add Postgres bulk migration path#94

Merged
djkazic merged 7 commits into
lightninglabs:mainfrom
ziggie1984:lndinit-sqlbase-migration-bulk
Jul 20, 2026
Merged

migratekvdb: add Postgres bulk migration path#94
djkazic merged 7 commits into
lightninglabs:mainfrom
ziggie1984:lndinit-sqlbase-migration-bulk

Conversation

@ziggie1984

@ziggie1984 ziggie1984 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR wires lndinit to the migration-only SQL KV bulk migration API from
lightningnetwork/lnd#10965 and adds a Postgres-only bulk migration path.

It keeps lndinit as orchestration code:

  • updates the dependency stack to the lnd PR head and btcd split modules
  • records interrupted bulk attempts in the migration meta DB
  • calls the lnd-owned SQL KV bulk writer/verifier APIs
  • adds --bulk-writes for fresh Postgres migrations
  • fails closed when interrupted recovery finds a non-empty target
  • adds --reset-bulk-target for explicitly authorized destructive recovery
  • isolates the bulk implementation behind the kvdb_postgres build constraint
  • documents the flags and recovery behavior

An interrupted migration whose transaction left the target empty restarts
automatically. If the target contains rows, recovery refuses to modify it unless
the operator explicitly supplies --reset-bulk-target, which truncates the
target before restarting.

Dependency

This PR depends on lightningnetwork/lnd#10965.

The temporary replace directives in go.mod should be removed once that PR
lands and lndinit can depend on upstream lnd module versions.

Verification

go test ./migratekvdb
go test -tags "kvdb_etcd kvdb_postgres kvdb_sqlite" ./migratekvdb
go test -tags "kvdb_etcd kvdb_postgres kvdb_sqlite" . \
  -run '^TestValidateBulkResetFlags$'
make lint
git diff --check

@ziggie1984
ziggie1984 force-pushed the lndinit-sqlbase-migration-bulk branch from debdd4c to 72cbef2 Compare July 13, 2026 14:48
@ziggie1984
ziggie1984 marked this pull request as ready for review July 13, 2026 14:59
@ziggie1984 ziggie1984 self-assigned this Jul 13, 2026
@ziggie1984

Copy link
Copy Markdown
Contributor Author

/gateway review

@ziggie1984

Copy link
Copy Markdown
Contributor Author

/gateway review

@lightninglabs-gateway lightninglabs-gateway Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gateway review — 9 findings

🔴 0 Blocker · 🟠 4 Major · 🟡 5 Minor · 🔵 0 Nit

Summary

This PR wires lndinit to lnd's new Postgres-only bulk SQL-KV migration API, adding a MigrateBulk/VerifyMigrationBulk path behind a --bulk-writes flag plus a BulkInProgress recovery marker. The orchestration code is clean and the new logic is unit-tested for recovery, fresh-target rejection, and target-corruption detection.

The central risk is in the verification path, which is the gate that lets an operator tombstone a live source DB. VerifyMigrationBulk compares the bolt source cursor positionally against the rows returned by the lnd-owned FetchChildren, so its soundness rests on an ordering contract that is defined only in the (currently vendored-by-replace) fork and cannot be audited here. Separately, the interrupted-run recovery path unconditionally truncates the destination based on source-side state alone, and an explicitly-requested --bulk-writes is silently downgraded on non-Postgres destinations. Verdict is REQUEST_CHANGES.

Bot commands
  • /gateway re-review — re-run after pushing changes
  • /gateway dismiss <id> — silence a finding (maintainers)
  • /gateway explain <id> — elaborate on a finding (anyone)

Comment thread cmd_migrate_db.go Outdated
Comment thread go.mod Outdated
Comment thread migratekvdb/bulk_sql.go
Comment thread migratekvdb/bulk_sql.go
Comment thread cmd_migrate_db.go Outdated
Comment thread migratekvdb/bulk_sql.go
Comment thread migratekvdb/bulk_sql.go
Comment thread migratekvdb/bulk_sql.go
Comment thread migratekvdb/bulk_sql.go
@lightninglabs-gateway

Copy link
Copy Markdown

🤖 gateway audit metadata for this PR — auto-generated, please don't edit.

@saubyk
saubyk requested a review from djkazic July 14, 2026 16:40
@ziggie1984
ziggie1984 force-pushed the lndinit-sqlbase-migration-bulk branch 2 times, most recently from 2f7a506 to 485971a Compare July 16, 2026 16:14
@ziggie1984

Copy link
Copy Markdown
Contributor Author

/gateway re-review

@lightninglabs-gateway

Copy link
Copy Markdown

🔁 gateway re-review starting…

@ziggie1984

Copy link
Copy Markdown
Contributor Author

A benchmark I did so far locally just for info

  ### Initial results

   Path             Time    Throughput          Key rate        Memory          Allocations
  ━━━━━━━━━━  ━━━━━━━━━━━  ━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━
   Standard    779 ms/op     6.78 MB/s     25,664 keys/s    80.8 MB/op    622,755 allocs/op
  ──────────  ───────────  ────────────  ────────────────  ────────────  ───────────────────
   Bulk        131 ms/op    40.31 MB/s    152,698 keys/s    16.6 MB/op    152,103 allocs/op

  For this dataset and machine, the bulk path was approximately 5.9× faster, used about 4.9× less allocated memory, and performed about 4.1×
  fewer allocations.

  These are exploratory numbers rather than a stable performance guarantee. This was a single iteration on one machine using a synthetic,
  leaf-heavy dataset. A more rigorous comparison would use repeated runs with benchstat, alternate execution order, warmed PostgreSQL state,
  and multiple dataset shapes and sizes. Nevertheless, the initial result confirms that the bulk implementation moves performance in the
  intended direction.

@ziggie1984

Copy link
Copy Markdown
Contributor Author

/gateway re-review

@ziggie1984
ziggie1984 force-pushed the lndinit-sqlbase-migration-bulk branch 3 times, most recently from 29af421 to b9d25f6 Compare July 17, 2026 02:51
Bump the Go directive in both the main module and the tools module to
1.25.11 so the whole project builds and lints against a single, current
toolchain. The tools module was still pinned to 1.25.4 and is now
aligned with the main module.
@ziggie1984
ziggie1984 force-pushed the lndinit-sqlbase-migration-bulk branch from b9d25f6 to b48557c Compare July 17, 2026 03:01
@djkazic

djkazic commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Version check passed (not a version issue). The migrate phase was flying (~114k keys/sec) then postgres ran out of shared memory.

The likely root cause: the upstream BeginBulk opens the whole migration as one SERIALIZABLE transaction (migration_bulk_postgres.go:91-92), and a serializable transaction inserting millions of rows accumulates predicate locks until it exhausts shared memory on default postgres settings. The original path I laid out used the default READ COMMITTED and completed on the same vanilla config.

@djkazic

djkazic commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

No-tag build breaks: cmd_migrate_db.go is untagged but references sqlbase.MigrationBackend, postgres.NewMigrationBackend, and the kvdb_postgres-only MigrateBulk/VerifyMigrationBulk. go build ./... without tags fails.

Fix: move the bulk wiring into a //go:build kvdb_postgres file with a small non-postgres stub. (Mine avoided this by using database/sql generically in the untagged cmd.)

@ziggie1984
ziggie1984 force-pushed the lndinit-sqlbase-migration-bulk branch 2 times, most recently from 780018d to 5d53429 Compare July 17, 2026 23:12
btcd has split several of its packages into independently versioned
modules. Move the direct imports over to the new module paths --
chaincfg/v2 in cmd_init_wallet.go and btcutil/v2 in cmd_migrate_db.go --
and update go.mod/go.sum accordingly.

This pulls in the v2 lines of the btcd sub-modules (btcutil, chaincfg,
btcec, chainhash, psbt, txscript, wire, address) together with the
matching btcwallet 0.18.0 and kvdb 1.6.1, plus a broad refresh of the
indirect dependency set. Note that lnd is pinned to a pseudo-version
(v0.21.0-beta.rc2.0.20260717013942-f02cf4c470c9) rather than a tagged
release -- a commit-hashed build is required until the split-module work
lands in a tagged lnd. The kvdb bump is what exposes the SQL KV bulk
migration API used by the following commits.
Add a BulkInProgress flag to the persisted migration state. The
fresh-only bulk path sets this marker before it writes any rows to the
target database and clears it once the write completes.

The marker is what makes interrupted bulk runs recoverable: if the
process dies mid-write, the flag is still set on the next start, which
signals that the target holds a partial migration and must be reset
before the bulk migration can start over from the source.
Add MigrateBulk and VerifyMigrationBulk, a faster bolt-to-SQL KV
migration that goes through the destination backend's migration-only
bulk API instead of the generic per-key kvdb writes.

Migration walks the source bucket tree depth-first. Bucket rows are
inserted immediately so their generated SQL ids can serve as parent ids
for their children, while leaf rows are buffered in a bulkWriter and
flushed in batches to amortize the insert cost.

The path is intentionally not resumable mid-bucket. Before writing it
sets the BulkInProgress marker; if a previous run left that marker
behind, the target is reset through the lnd-owned API and the migration
restarts from the source rather than trying to patch a partial write. A
non-empty target from an interrupted run is only truncated when the
caller explicitly authorizes the reset.

Verification mirrors the migration: it matches source top-level buckets
to target rows and then compares their direct children batch by batch
using the backend's batched verification API, validating the target
without a second full per-key scan.
Add coverage for the bulk SQL KV migration path, focusing on the
crash-recovery contract established by the BulkInProgress marker.

The tests exercise recovery of an interrupted attempt, the guard that
refuses to reset a non-empty target unless the reset was explicitly
authorized, the check that a fresh migration requires an empty target,
and that bulk verification detects a corrupted target.
Expose the bulk migration path through the migrate-db command via two
new flags: --bulk-writes to opt into the fresh-only Postgres bulk path,
and --reset-bulk-target to authorize recovery from an interrupted run.

When --bulk-writes is set the destination is opened with its
migration-only bulk capability (sqlbase.MigrationBackend), and the
command dispatches to MigrateBulk/VerifyMigrationBulk instead of the
default per-key migration.

Validation keeps the flag combinations safe: --bulk-writes requires a
postgres destination, --reset-bulk-target requires --bulk-writes, and
--reset-bulk-target cannot be combined with --force-new-migration, since
the two describe conflicting ways to restart. Adds unit and
postgres-backed tests for the new wiring and validation.
Document the new --bulk-writes and --reset-bulk-target flags in the
data-migration guide. Explain that the bulk path is a fresh-only, faster
Postgres migration that is not resumable mid-write, how automatic
recovery works when an interrupted run left the target empty, and that a
non-empty target fails closed until the operator explicitly authorizes
truncating and destroying the destination table with --reset-bulk-target.

Also refresh the generated help output to include both new flags.
@ziggie1984
ziggie1984 force-pushed the lndinit-sqlbase-migration-bulk branch from 5d53429 to 0b93da4 Compare July 18, 2026 02:33
@djkazic

djkazic commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

tACK. Migration of our large test DB took 8m27s.

@djkazic
djkazic merged commit be7ce55 into lightninglabs:main Jul 20, 2026
2 checks passed
@ziggie1984
ziggie1984 deleted the lndinit-sqlbase-migration-bulk branch July 21, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants