sqldb: add benchmark for sqlite connection number
What changed, and why it matters
This commit only adds two benchmark tests that measure how fast SQLite reads perform with different numbers of database connections. It does not change any production code, configuration defaults, or behavior of the LND software. There is no security issue here.
No action required. This is a test-only benchmark addition with no security relevance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces sqldb/sqlite_bench_test.go containing BenchmarkSqliteMaxConns and BenchmarkSqliteMaxConnsConcurrentReads. These benchmarks create a temporary SQLite database, insert 500 invoices, and compare read throughput across MaxConnections values of 1, 2, 4, 8, 16, and the library default. The code is test-only, uses b.TempDir() for isolated storage, and does not modify the DefaultSqliteMaxConns constant or any runtime logic.
Changed components
sqldb/sqlite_bench_test.goInspect captured patch +204 / −0
diff --git a/sqldb/sqlite_bench_test.go b/sqldb/sqlite_bench_test.go
new file mode 100644
index 0000000..bc5cea3
--- /dev/null
+++ b/sqldb/sqlite_bench_test.go
@@ -0,0 +1,204 @@
+//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64))
+
+package sqldb
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "path/filepath"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/lightningnetwork/lnd/sqldb/sqlc"
+ "github.com/stretchr/testify/require"
+)
+
+// BenchmarkSqliteMaxConns benchmarks sequential reads against a SQLite
+// database with varying MaxConnections settings.
+//
+// Run with:
+//
+// go test -bench=BenchmarkSqliteMaxConns -benchmem -run=^$ ./sqldb/
+func BenchmarkSqliteMaxConns(b *testing.B) {
+ const numInvoices = 500
+
+ // connCounts contains the MaxConnections values we want to compare.
+ // 0 means "use the library default" (currently 2 for SQLite).
+ connCounts := []int{1, 2, 4, 8, 16, 0}
+
+ // Build a fresh SQLite database that will be shared across all
+ // sub-benchmarks. We insert a fixed set of invoices once and then
+ // execute read-only queries from multiple goroutines.
+ dbFileName := filepath.Join(b.TempDir(), "bench.db")
+
+ // Open the store once with migrations so the schema is in place.
+ setupStore, err := NewSqliteStore(&SqliteConfig{
+ SkipMigrations: false,
+ }, dbFileName)
+ require.NoError(b, err)
+
+ require.NoError(b, setupStore.ApplyAllMigrations(
+ context.Background(), GetMigrations(),
+ ))
+
+ ctx := context.Background()
+
+ // Insert test invoices. We use a predictable hash per invoice so we
+ // can look them up deterministically during the benchmark.
+ hashes := make([][]byte, numInvoices)
+ for i := range numInvoices {
+ hash := make([]byte, 32)
+ hash[0] = byte(i)
+ hash[1] = byte(i >> 8)
+ hashes[i] = hash
+
+ _, err := setupStore.InsertInvoice(
+ ctx, sqlc.InsertInvoiceParams{
+ Hash: hash,
+ PaymentAddr: hash,
+ PaymentRequestHash: hash,
+ Expiry: 3600,
+ CreatedAt: time.Now(),
+ },
+ )
+ require.NoError(b, err)
+ }
+
+ require.NoError(b, setupStore.DB.Close())
+
+ for _, maxConns := range connCounts {
+ name := fmt.Sprintf("MaxConns=%d", maxConns)
+ if maxConns == 0 {
+ name = fmt.Sprintf("MaxConns=default(%d)",
+ DefaultSqliteMaxConns)
+ }
+
+ b.Run(name, func(b *testing.B) {
+ store, err := NewSqliteStore(
+ &SqliteConfig{
+ SkipMigrations: true,
+ MaxConnections: maxConns,
+ }, dbFileName,
+ )
+ require.NoError(b, err)
+
+ b.Cleanup(func() {
+ require.NoError(b, store.DB.Close())
+ })
+
+ var i int
+ for b.Loop() {
+ hash := hashes[i%numInvoices]
+ i++
+
+ _, err := store.GetInvoice(
+ ctx,
+ sqlc.GetInvoiceParams{
+ Hash: hash,
+ },
+ )
+ if err != nil {
+ require.ErrorIs(b, err, sql.ErrNoRows)
+ }
+ }
+ })
+ }
+}
+
+// BenchmarkSqliteMaxConnsConcurrentReads measures aggregate read throughput
+// for a fixed level of goroutine concurrency to complement the sequential
+// benchmark above. Each iteration launches a fixed number of goroutines that
+// all issue reads simultaneously, directly stressing the connection pool.
+func BenchmarkSqliteMaxConnsConcurrentReads(b *testing.B) {
+ const (
+ numInvoices = 500
+ goroutines = 16
+ )
+
+ connCounts := []int{1, 2, 4, 8, 16, 0}
+
+ dbFileName := filepath.Join(b.TempDir(), "bench_conc.db")
+
+ setupStore, err := NewSqliteStore(&SqliteConfig{
+ SkipMigrations: false,
+ }, dbFileName)
+ require.NoError(b, err)
+
+ require.NoError(b, setupStore.ApplyAllMigrations(
+ context.Background(), GetMigrations(),
+ ))
+
+ ctx := context.Background()
+
+ hashes := make([][]byte, numInvoices)
+ for i := range numInvoices {
+ hash := make([]byte, 32)
+ hash[0] = byte(i)
+ hash[1] = byte(i >> 8)
+ hashes[i] = hash
+
+ _, err := setupStore.InsertInvoice(
+ ctx, sqlc.InsertInvoiceParams{
+ Hash: hash,
+ PaymentAddr: hash,
+ PaymentRequestHash: hash,
+ Expiry: 3600,
+ CreatedAt: time.Now(),
+ },
+ )
+ require.NoError(b, err)
+ }
+
+ require.NoError(b, setupStore.DB.Close())
+
+ for _, maxConns := range connCounts {
+ name := fmt.Sprintf("MaxConns=%d", maxConns)
+ if maxConns == 0 {
+ name = fmt.Sprintf("MaxConns=default(%d)",
+ DefaultSqliteMaxConns)
+ }
+
+ b.Run(name, func(b *testing.B) {
+ store, err := NewSqliteStore(
+ &SqliteConfig{
+ SkipMigrations: true,
+ MaxConnections: maxConns,
+ }, dbFileName,
+ )
+ require.NoError(b, err)
+
+ b.Cleanup(func() {
+ require.NoError(b, store.DB.Close())
+ })
+
+ for b.Loop() {
+ var wg sync.WaitGroup
+ wg.Add(goroutines)
+
+ for g := range goroutines {
+ go func() {
+ defer wg.Done()
+
+ hash := hashes[g%numInvoices]
+ _, err := store.GetInvoice(
+ ctx,
+ sqlc.GetInvoiceParams{
+ Hash: hash,
+ },
+ )
+ if err != nil &&
+ err != sql.ErrNoRows {
+
+ b.Errorf("GetInvoice:"+
+ " %v", err)
+ }
+ }()
+ }
+
+ wg.Wait()
+ }
+ })
+ }
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.