Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

graph: extract cache from CRUD [2] #9545

Merged
merged 3 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion autopilot/prefattach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func newDiskChanGraph(t *testing.T) (testGraph, error) {
})
require.NoError(t, err)

graphDB, err := graphdb.NewChannelGraph(backend)
graphDB, err := graphdb.NewChannelGraph(&graphdb.Config{KVDB: backend})
require.NoError(t, err)

return &testDBGraph{
Expand Down
16 changes: 10 additions & 6 deletions config_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1026,26 +1026,30 @@ func (d *DefaultDatabaseBuilder) BuildDatabase(
"instances")
}

graphDBOptions := []graphdb.OptionModifier{
graphDBOptions := []graphdb.KVStoreOptionModifier{
graphdb.WithRejectCacheSize(cfg.Caches.RejectCacheSize),
graphdb.WithChannelCacheSize(cfg.Caches.ChannelCacheSize),
graphdb.WithBatchCommitInterval(cfg.DB.BatchCommitInterval),
}

chanGraphOpts := []graphdb.ChanGraphOption{
graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache),
}

// We want to pre-allocate the channel graph cache according to what we
// expect for mainnet to speed up memory allocation.
if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
graphDBOptions = append(
graphDBOptions, graphdb.WithPreAllocCacheNumNodes(
chanGraphOpts = append(
chanGraphOpts, graphdb.WithPreAllocCacheNumNodes(
graphdb.DefaultPreAllocCacheNumNodes,
),
)
}

dbs.GraphDB, err = graphdb.NewChannelGraph(
databaseBackends.GraphDB, graphDBOptions...,
)
dbs.GraphDB, err = graphdb.NewChannelGraph(&graphdb.Config{
KVDB: databaseBackends.GraphDB,
KVStoreOpts: graphDBOptions,
}, chanGraphOpts...)
if err != nil {
cleanUp()

Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/release-notes-0.19.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ The underlying functionality between those two options remain the same.
- [Refactor to hide DB transactions](https://github.com/lightningnetwork/lnd/pull/9513)
- Move the graph cache out of the graph CRUD layer:
- [1](https://github.com/lightningnetwork/lnd/pull/9533)
- [2](https://github.com/lightningnetwork/lnd/pull/9545)

* [Golang was updated to
`v1.22.11`](https://github.com/lightningnetwork/lnd/pull/9462).
Expand Down
75 changes: 71 additions & 4 deletions graph/db/graph.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,93 @@
package graphdb

import "github.com/lightningnetwork/lnd/kvdb"
import (
"time"

"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)

// Config is a struct that holds all the necessary dependencies for a
// ChannelGraph.
type Config struct {
Copy link
Member

Choose a reason for hiding this comment

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

nit: KVDBConfig? No strong opinion here tho, as this is a place I find myself struggling sometimes. It's nice to just name it Config, as suggested by the official go, and use it as graphdb.Config. But in practice it's easier to search using a unique name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

so this is the config struct for ChannelGraph and not for the KVStore. It just at the moment happens to only hold KVStore related things but will be expanded in future.

// KVDB is the kvdb.Backend that will be used for initializing the
// KVStore CRUD layer.
KVDB kvdb.Backend

// KVStoreOpts is a list of functional options that will be used when
// initializing the KVStore.
KVStoreOpts []KVStoreOptionModifier
}

// ChannelGraph is a layer above the graph's CRUD layer.
//
// NOTE: currently, this is purely a pass-through layer directly to the backing
// KVStore. Upcoming commits will move the graph cache out of the KVStore and
// into this layer so that the KVStore is only responsible for CRUD operations.
type ChannelGraph struct {
graphCache *GraphCache

*KVStore
}

// NewChannelGraph creates a new ChannelGraph instance with the given backend.
func NewChannelGraph(db kvdb.Backend, options ...OptionModifier) (*ChannelGraph,
func NewChannelGraph(cfg *Config, options ...ChanGraphOption) (*ChannelGraph,
Copy link
Member

Choose a reason for hiding this comment

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

🎉

error) {

store, err := NewKVStore(db, options...)
opts := defaultChanGraphOptions()
for _, o := range options {
o(opts)
}

store, err := NewKVStore(cfg.KVDB, cfg.KVStoreOpts...)
Copy link
Member

Choose a reason for hiding this comment

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

it looks like NewKVStore can just take the Config, maybe in another PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

see above comment about Config struct being ChannelGraph config and not KVStore config.

We could add a KVStore config struct and then have the COnfig struct hold that struct but i think we can also after this series unexport anything KVStore related anyways so then would probs not make sense to have an exported KVStore config struct

if err != nil {
return nil, err
}

if !opts.useGraphCache {
return &ChannelGraph{
KVStore: store,
}, nil
}

// The graph cache can be turned off (e.g. for mobile users) for a
// speed/memory usage tradeoff.
graphCache := NewGraphCache(opts.preAllocCacheNumNodes)
startTime := time.Now()
log.Debugf("Populating in-memory channel graph, this might take a " +
"while...")

err = store.ForEachNodeCacheable(func(node route.Vertex,
features *lnwire.FeatureVector) error {

graphCache.AddNodeFeatures(node, features)

return nil
})
if err != nil {
return nil, err
}

err = store.ForEachChannel(func(info *models.ChannelEdgeInfo,
policy1, policy2 *models.ChannelEdgePolicy) error {

graphCache.AddChannel(info, policy1, policy2)

return nil
})
if err != nil {
return nil, err
}

log.Debugf("Finished populating in-memory channel graph (took %v, %s)",
time.Since(startTime), graphCache.Stats())

store.setGraphCache(graphCache)

return &ChannelGraph{
KVStore: store,
KVStore: store,
graphCache: graphCache,
}, nil
}
5 changes: 3 additions & 2 deletions graph/db/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3924,6 +3924,7 @@ func TestGraphCacheForEachNodeChannel(t *testing.T) {
// Unset the channel graph cache to simulate the user running with the
// option turned off.
graph.graphCache = nil
graph.KVStore.graphCache = nil

node1, err := createTestVertex(graph.db)
require.Nil(t, err)
Expand Down Expand Up @@ -4005,7 +4006,7 @@ func TestGraphLoading(t *testing.T) {
defer backend.Close()
defer backendCleanup()

graph, err := NewChannelGraph(backend)
graph, err := NewChannelGraph(&Config{KVDB: backend})
require.NoError(t, err)

// Populate the graph with test data.
Expand All @@ -4015,7 +4016,7 @@ func TestGraphLoading(t *testing.T) {

// Recreate the graph. This should cause the graph cache to be
// populated.
graphReloaded, err := NewChannelGraph(backend)
graphReloaded, err := NewChannelGraph(&Config{KVDB: backend})
require.NoError(t, err)

// Assert that the cache content is identical.
Expand Down
54 changes: 16 additions & 38 deletions graph/db/kv_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ type KVStore struct {

// NewKVStore allocates a new KVStore backed by a DB instance. The
// returned instance has its own unique reject cache and channel cache.
func NewKVStore(db kvdb.Backend, options ...OptionModifier) (*KVStore,
func NewKVStore(db kvdb.Backend, options ...KVStoreOptionModifier) (*KVStore,
error) {

opts := DefaultOptions()
Expand All @@ -224,43 +224,18 @@ func NewKVStore(db kvdb.Backend, options ...OptionModifier) (*KVStore,
db, nil, opts.BatchCommitInterval,
)

// The graph cache can be turned off (e.g. for mobile users) for a
// speed/memory usage tradeoff.
if opts.UseGraphCache {
g.graphCache = NewGraphCache(opts.PreAllocCacheNumNodes)
startTime := time.Now()
log.Debugf("Populating in-memory channel graph, this might " +
"take a while...")

err := g.ForEachNodeCacheable(func(node route.Vertex,
features *lnwire.FeatureVector) error {

g.graphCache.AddNodeFeatures(node, features)

return nil
})
if err != nil {
return nil, err
}

err = g.ForEachChannel(func(info *models.ChannelEdgeInfo,
policy1, policy2 *models.ChannelEdgePolicy) error {

g.graphCache.AddChannel(info, policy1, policy2)

return nil
})
if err != nil {
return nil, err
}

log.Debugf("Finished populating in-memory channel graph (took "+
"%v, %s)", time.Since(startTime), g.graphCache.Stats())
}

return g, nil
}

// setGraphCache sets the KVStore's graphCache.
//
// NOTE: this is temporary and will only be called from the ChannelGraph's
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

// constructor before the KVStore methods are available to be called. This will
// be removed once the graph cache is fully owned by the ChannelGraph.
func (c *KVStore) setGraphCache(cache *GraphCache) {
c.graphCache = cache
}

// channelMapKey is the key structure used for storing channel edge policies.
type channelMapKey struct {
nodeKey route.Vertex
Expand Down Expand Up @@ -4827,8 +4802,8 @@ func (c *chanGraphNodeTx) ForEachChannel(f func(*models.ChannelEdgeInfo,

// MakeTestGraph creates a new instance of the KVStore for testing
// purposes.
func MakeTestGraph(t testing.TB, modifiers ...OptionModifier) (*ChannelGraph,
error) {
func MakeTestGraph(t testing.TB, modifiers ...KVStoreOptionModifier) (
*ChannelGraph, error) {

opts := DefaultOptions()
for _, modifier := range modifiers {
Expand All @@ -4843,7 +4818,10 @@ func MakeTestGraph(t testing.TB, modifiers ...OptionModifier) (*ChannelGraph,
return nil, err
}

graph, err := NewChannelGraph(backend)
graph, err := NewChannelGraph(&Config{
KVDB: backend,
KVStoreOpts: modifiers,
})
if err != nil {
backendCleanup()

Expand Down
99 changes: 58 additions & 41 deletions graph/db/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,49 @@ const (
DefaultPreAllocCacheNumNodes = 15000
)

// Options holds parameters for tuning and customizing a graph.DB.
type Options struct {
// chanGraphOptions holds parameters for tuning and customizing the
// ChannelGraph.
type chanGraphOptions struct {
// useGraphCache denotes whether the in-memory graph cache should be
// used or a fallback version that uses the underlying database for
// path finding.
useGraphCache bool

// preAllocCacheNumNodes is the number of nodes we expect to be in the
// graph cache, so we can pre-allocate the map accordingly.
preAllocCacheNumNodes int
}

// defaultChanGraphOptions returns a new chanGraphOptions instance populated
// with default values.
func defaultChanGraphOptions() *chanGraphOptions {
return &chanGraphOptions{
useGraphCache: true,
preAllocCacheNumNodes: DefaultPreAllocCacheNumNodes,
}
}

// ChanGraphOption describes the signature of a functional option that can be
// used to customize a ChannelGraph instance.
type ChanGraphOption func(*chanGraphOptions)

// WithUseGraphCache sets whether the in-memory graph cache should be used.
func WithUseGraphCache(use bool) ChanGraphOption {
return func(o *chanGraphOptions) {
o.useGraphCache = use
}
}

// WithPreAllocCacheNumNodes sets the number of nodes we expect to be in the
// graph cache, so we can pre-allocate the map accordingly.
func WithPreAllocCacheNumNodes(n int) ChanGraphOption {
return func(o *chanGraphOptions) {
o.preAllocCacheNumNodes = n
}
}

// KVStoreOptions holds parameters for tuning and customizing a graph.DB.
type KVStoreOptions struct {
// RejectCacheSize is the maximum number of rejectCacheEntries to hold
// in the rejection cache.
RejectCacheSize int
Expand All @@ -34,67 +75,43 @@ type Options struct {
// wait before attempting to commit a pending set of updates.
BatchCommitInterval time.Duration

// PreAllocCacheNumNodes is the number of nodes we expect to be in the
// graph cache, so we can pre-allocate the map accordingly.
PreAllocCacheNumNodes int

// UseGraphCache denotes whether the in-memory graph cache should be
// used or a fallback version that uses the underlying database for
// path finding.
UseGraphCache bool

// NoMigration specifies that underlying backend was opened in read-only
// mode and migrations shouldn't be performed. This can be useful for
// applications that use the channeldb package as a library.
NoMigration bool
}

// DefaultOptions returns an Options populated with default values.
func DefaultOptions() *Options {
return &Options{
RejectCacheSize: DefaultRejectCacheSize,
ChannelCacheSize: DefaultChannelCacheSize,
PreAllocCacheNumNodes: DefaultPreAllocCacheNumNodes,
UseGraphCache: true,
NoMigration: false,
// DefaultOptions returns a KVStoreOptions populated with default values.
func DefaultOptions() *KVStoreOptions {
return &KVStoreOptions{
RejectCacheSize: DefaultRejectCacheSize,
ChannelCacheSize: DefaultChannelCacheSize,
NoMigration: false,
}
}

// OptionModifier is a function signature for modifying the default Options.
type OptionModifier func(*Options)
// KVStoreOptionModifier is a function signature for modifying the default
// KVStoreOptions.
type KVStoreOptionModifier func(*KVStoreOptions)

// WithRejectCacheSize sets the RejectCacheSize to n.
func WithRejectCacheSize(n int) OptionModifier {
return func(o *Options) {
func WithRejectCacheSize(n int) KVStoreOptionModifier {
return func(o *KVStoreOptions) {
o.RejectCacheSize = n
}
}

// WithChannelCacheSize sets the ChannelCacheSize to n.
func WithChannelCacheSize(n int) OptionModifier {
return func(o *Options) {
func WithChannelCacheSize(n int) KVStoreOptionModifier {
return func(o *KVStoreOptions) {
o.ChannelCacheSize = n
}
}

// WithPreAllocCacheNumNodes sets the PreAllocCacheNumNodes to n.
func WithPreAllocCacheNumNodes(n int) OptionModifier {
return func(o *Options) {
o.PreAllocCacheNumNodes = n
}
}

// WithBatchCommitInterval sets the batch commit interval for the interval batch
// schedulers.
func WithBatchCommitInterval(interval time.Duration) OptionModifier {
return func(o *Options) {
func WithBatchCommitInterval(interval time.Duration) KVStoreOptionModifier {
return func(o *KVStoreOptions) {
o.BatchCommitInterval = interval
}
}

// WithUseGraphCache sets the UseGraphCache option to the given value.
func WithUseGraphCache(use bool) OptionModifier {
return func(o *Options) {
o.UseGraphCache = use
}
}
Loading
Loading