graph/db: migrate zombie index to SQL

This commit expands `MigrateGraphToSQL` to include migration of the
zombie index.

NOTE: we take this opportunity to clean up the zombie index a bit by
first checking for each channel in the zombie index if it has been
marked as closed in the closed-scid index. If it has, then there is no
need to include it in the zombie index. A special case test for this
is added too.
This commit is contained in:
Elle Mouton committed 2025-07-11 09:36:17 +02:00
1 parent 0cb17bbbe5
commit 8b82a5c8fe
2 files changed
+237 -2

No files matched your search

+108
View File
@@ -70,6 +70,11 @@ func MigrateGraphToSQL(ctx context.Context, kvBackend kvdb.Backend,
err)
}
// 6) Migrate the zombie index.
if err := migrateZombieIndex(ctx, kvBackend, sqlDB); err != nil {
return fmt.Errorf("could not migrate zombie index: %w", err)
}
log.Infof("Finished migration of the graph store from KV to SQL in %v",
time.Since(t0))
@@ -741,6 +746,109 @@ func migrateClosedSCIDIndex(ctx context.Context, kvBackend kvdb.Backend,
return nil
}
// migrateZombieIndex migrates the zombie index from the KV backend to
// the SQL database. It iterates over each zombie channel in the KV store,
// inserts it into the SQL database, and then verifies that the channel is
// indeed marked as a zombie channel in the SQL database.
//
// NOTE: before inserting an entry into the zombie index, the function checks
// if the channel is already marked as closed in the SQL store. If it is,
// the entry is skipped. This means that the resulting zombie index count in
// the SQL store may well be less than the count of zombie channels in the KV
// store.
func migrateZombieIndex(ctx context.Context, kvBackend kvdb.Backend,
sqlDB SQLQueries) error {
var count uint64
err := forEachZombieEntry(kvBackend, func(chanID uint64, pubKey1,
pubKey2 [33]byte) error {
chanIDB := channelIDToBytes(chanID)
// If it is in the closed SCID index, we don't need to
// add it to the zombie index.
//
// NOTE: this means that the resulting zombie index count in
// the SQL store may well be less than the count of zombie
// channels in the KV store.
isClosed, err := sqlDB.IsClosedChannel(ctx, chanIDB)
if err != nil {
return fmt.Errorf("could not check closed "+
"channel: %w", err)
}
if isClosed {
return nil
}
count++
err = sqlDB.UpsertZombieChannel(
ctx, sqlc.UpsertZombieChannelParams{
Version: int16(ProtocolV1),
Scid: chanIDB,
NodeKey1: pubKey1[:],
NodeKey2: pubKey2[:],
},
)
if err != nil {
return fmt.Errorf("could not upsert zombie "+
"channel %d: %w", chanID, err)
}
// Finally, verify that the channel is indeed marked as a
// zombie channel.
isZombie, err := sqlDB.IsZombieChannel(
ctx, sqlc.IsZombieChannelParams{
Version: int16(ProtocolV1),
Scid: chanIDB,
},
)
if err != nil {
return fmt.Errorf("could not check if "+
"channel %d is zombie: %w", chanID, err)
}
if !isZombie {
return fmt.Errorf("channel %d should be "+
"a zombie, but is not", chanID)
}
return nil
})
if err != nil {
return fmt.Errorf("could not migrate zombie index: %w", err)
}
log.Infof("Migrated %d zombie channels from KV to SQL", count)
return nil
}
// forEachZombieEntry iterates over each zombie channel entry in the
// KV backend and calls the provided callback function for each entry.
func forEachZombieEntry(db kvdb.Backend, cb func(chanID uint64, pubKey1,
pubKey2 [33]byte) error) error {
return kvdb.View(db, func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
zombieIndex := edges.NestedReadBucket(zombieBucket)
if zombieIndex == nil {
return nil
}
return zombieIndex.ForEach(func(k, v []byte) error {
var pubKey1, pubKey2 [33]byte
copy(pubKey1[:], v[:33])
copy(pubKey2[:], v[33:])
return cb(byteOrder.Uint64(k), pubKey1, pubKey2)
})
}, func() {})
}
// forEachClosedSCID iterates over each closed SCID in the KV backend and calls
// the provided callback function for each SCID.
func forEachClosedSCID(db kvdb.Backend,