accounts+sqlc: improve ListAccounts for SQL store

Optimize SQL account listing by preloading linked invoices and payments
for all accounts in bulk.

Before this change, Accounts() queried ListAllAccounts and then did two
extra queries per account (ListAccountInvoices/ListAccountPayments),
which scales poorly as account count grows.

Add ListAllAccountInvoices and ListAllAccountPayments queries, group
their rows by account_id in memory, and marshal each account from the
preloaded data. Keep conversion logic shared through
marshalDBAccountWithLinkedData to preserve behavior between
single-account and list-account paths.

This reduces query count from 1 + 2N to 3 and improves list-path
performance without changing external semantics.
This commit is contained in:
Viktor Torstensson 2026-02-16 20:58:33 +01:00
parent 067eeafd7e
commit fb5b0af8d8
No known key found for this signature in database
GPG key ID: 961CC8259AE675D4
4 changed files with 118 additions and 10 deletions

View file

@ -44,6 +44,8 @@ type SQLQueries interface {
GetAccountIndex(ctx context.Context, name string) (int64, error)
GetAccountPayment(ctx context.Context, arg sqlc.GetAccountPaymentParams) (sqlc.AccountPayment, error)
InsertAccount(ctx context.Context, arg sqlc.InsertAccountParams) (int64, error)
ListAllAccountInvoices(ctx context.Context) ([]sqlc.AccountInvoice, error)
ListAllAccountPayments(ctx context.Context) ([]sqlc.AccountPayment, error)
ListAccountInvoices(ctx context.Context, id int64) ([]sqlc.AccountInvoice, error)
ListAccountPayments(ctx context.Context, id int64) ([]sqlc.AccountPayment, error)
ListAllAccounts(ctx context.Context) ([]sqlc.Account, error)
@ -200,6 +202,23 @@ func getAndMarshalAccount(ctx context.Context, db SQLQueries, id int64) (
func marshalDBAccount(ctx context.Context, db SQLQueries,
dbAcct sqlc.Account) (*OffChainBalanceAccount, error) {
invoices, err := db.ListAccountInvoices(ctx, dbAcct.ID)
if err != nil {
return nil, err
}
payments, err := db.ListAccountPayments(ctx, dbAcct.ID)
if err != nil {
return nil, err
}
return marshalDBAccountWithLinkedData(dbAcct, invoices, payments)
}
func marshalDBAccountWithLinkedData(dbAcct sqlc.Account,
invoices []sqlc.AccountInvoice,
payments []sqlc.AccountPayment) (*OffChainBalanceAccount, error) {
alias, err := AccountIDFromInt64(dbAcct.Alias)
if err != nil {
return nil, err
@ -217,21 +236,12 @@ func marshalDBAccount(ctx context.Context, db SQLQueries,
Label: dbAcct.Label.String,
}
invoices, err := db.ListAccountInvoices(ctx, dbAcct.ID)
if err != nil {
return nil, err
}
for _, invoice := range invoices {
var hash lntypes.Hash
copy(hash[:], invoice.Hash)
account.Invoices[hash] = struct{}{}
}
payments, err := db.ListAccountPayments(ctx, dbAcct.ID)
if err != nil {
return nil, err
}
for _, payment := range payments {
var hash lntypes.Hash
copy(hash[:], payment.Hash)
@ -560,9 +570,36 @@ func (s *SQLStore) Accounts(ctx context.Context) ([]*OffChainBalanceAccount,
return err
}
dbInvoices, err := db.ListAllAccountInvoices(ctx)
if err != nil {
return err
}
dbPayments, err := db.ListAllAccountPayments(ctx)
if err != nil {
return err
}
accountInvoices := make(map[int64][]sqlc.AccountInvoice)
for _, invoice := range dbInvoices {
accountInvoices[invoice.AccountID] = append(
accountInvoices[invoice.AccountID], invoice,
)
}
accountPayments := make(map[int64][]sqlc.AccountPayment)
for _, payment := range dbPayments {
accountPayments[payment.AccountID] = append(
accountPayments[payment.AccountID], payment,
)
}
accounts = make([]*OffChainBalanceAccount, len(dbAccounts))
for i, dbAccount := range dbAccounts {
account, err := marshalDBAccount(ctx, db, dbAccount)
account, err := marshalDBAccountWithLinkedData(
dbAccount, accountInvoices[dbAccount.ID],
accountPayments[dbAccount.ID],
)
if err != nil {
return err
}

View file

@ -258,6 +258,67 @@ func (q *Queries) ListAccountPayments(ctx context.Context, accountID int64) ([]A
return items, nil
}
const listAllAccountInvoices = `-- name: ListAllAccountInvoices :many
SELECT account_id, hash
FROM account_invoices
`
func (q *Queries) ListAllAccountInvoices(ctx context.Context) ([]AccountInvoice, error) {
rows, err := q.db.QueryContext(ctx, listAllAccountInvoices)
if err != nil {
return nil, err
}
defer rows.Close()
var items []AccountInvoice
for rows.Next() {
var i AccountInvoice
if err := rows.Scan(&i.AccountID, &i.Hash); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAllAccountPayments = `-- name: ListAllAccountPayments :many
SELECT account_id, hash, status, full_amount_msat
FROM account_payments
`
func (q *Queries) ListAllAccountPayments(ctx context.Context) ([]AccountPayment, error) {
rows, err := q.db.QueryContext(ctx, listAllAccountPayments)
if err != nil {
return nil, err
}
defer rows.Close()
var items []AccountPayment
for rows.Next() {
var i AccountPayment
if err := rows.Scan(
&i.AccountID,
&i.Hash,
&i.Status,
&i.FullAmountMsat,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAllAccounts = `-- name: ListAllAccounts :many
SELECT id, alias, label, type, initial_balance_msat, current_balance_msat, last_updated, expiration
FROM accounts

View file

@ -58,6 +58,8 @@ type Querier interface {
InsertSessionPrivacyFlag(ctx context.Context, arg InsertSessionPrivacyFlagParams) error
ListAccountInvoices(ctx context.Context, accountID int64) ([]AccountInvoice, error)
ListAccountPayments(ctx context.Context, accountID int64) ([]AccountPayment, error)
ListAllAccountInvoices(ctx context.Context) ([]AccountInvoice, error)
ListAllAccountPayments(ctx context.Context) ([]AccountPayment, error)
ListAllAccounts(ctx context.Context) ([]Account, error)
ListAllKVStoresRecords(ctx context.Context) ([]Kvstore, error)
ListSessions(ctx context.Context) ([]Session, error)

View file

@ -76,11 +76,19 @@ SELECT *
FROM account_payments
WHERE account_id = $1;
-- name: ListAllAccountPayments :many
SELECT *
FROM account_payments;
-- name: ListAccountInvoices :many
SELECT *
FROM account_invoices
WHERE account_id = $1;
-- name: ListAllAccountInvoices :many
SELECT *
FROM account_invoices;
-- name: GetAccountInvoice :one
SELECT *
FROM account_invoices