mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-21 14:22:46 +02:00
sqldb+invoices: Optimize invoice fetching when the reference is only a hash
The current sqlc GetInvoice query experiences incremental slowdowns during the migration of large invoice databases, primarily due to its complex predicate set. For this specific use case, a streamlined GetInvoiceByHash function provides a more efficient solution, maintaining near-constant lookup times even with extensive table sizes.
This commit is contained in:
parent
b92f57e0ae
commit
94e2724a34
@ -187,6 +187,11 @@ func (r InvoiceRef) Modifier() RefModifier {
|
|||||||
return r.refModifier
|
return r.refModifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsHashOnly returns true if the invoice ref only contains a payment hash.
|
||||||
|
func (r InvoiceRef) IsHashOnly() bool {
|
||||||
|
return r.payHash != nil && r.payAddr == nil && r.setID == nil
|
||||||
|
}
|
||||||
|
|
||||||
// String returns a human-readable representation of an InvoiceRef.
|
// String returns a human-readable representation of an InvoiceRef.
|
||||||
func (r InvoiceRef) String() string {
|
func (r InvoiceRef) String() string {
|
||||||
var ids []string
|
var ids []string
|
||||||
|
@ -51,6 +51,9 @@ type SQLInvoiceQueries interface { //nolint:interfacebloat
|
|||||||
GetInvoice(ctx context.Context,
|
GetInvoice(ctx context.Context,
|
||||||
arg sqlc.GetInvoiceParams) ([]sqlc.Invoice, error)
|
arg sqlc.GetInvoiceParams) ([]sqlc.Invoice, error)
|
||||||
|
|
||||||
|
GetInvoiceByHash(ctx context.Context, hash []byte) (sqlc.Invoice,
|
||||||
|
error)
|
||||||
|
|
||||||
GetInvoiceBySetID(ctx context.Context, setID []byte) ([]sqlc.Invoice,
|
GetInvoiceBySetID(ctx context.Context, setID []byte) ([]sqlc.Invoice,
|
||||||
error)
|
error)
|
||||||
|
|
||||||
@ -354,22 +357,31 @@ func (i *SQLStore) AddInvoice(ctx context.Context,
|
|||||||
return newInvoice.AddIndex, nil
|
return newInvoice.AddIndex, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchInvoice fetches the common invoice data and the AMP state for the
|
// getInvoiceByRef fetches the invoice with the given reference. The reference
|
||||||
// invoice with the given reference.
|
// may be a payment hash, a payment address, or a set ID for an AMP sub invoice.
|
||||||
func fetchInvoice(ctx context.Context, db SQLInvoiceQueries,
|
func getInvoiceByRef(ctx context.Context,
|
||||||
ref InvoiceRef) (*Invoice, error) {
|
db SQLInvoiceQueries, ref InvoiceRef) (sqlc.Invoice, error) {
|
||||||
|
|
||||||
|
// If the reference is empty, we can't look up the invoice.
|
||||||
if ref.PayHash() == nil && ref.PayAddr() == nil && ref.SetID() == nil {
|
if ref.PayHash() == nil && ref.PayAddr() == nil && ref.SetID() == nil {
|
||||||
return nil, ErrInvoiceNotFound
|
return sqlc.Invoice{}, ErrInvoiceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// If the reference is a hash only, we can look up the invoice directly
|
||||||
invoice *Invoice
|
// by the payment hash which is faster.
|
||||||
params sqlc.GetInvoiceParams
|
if ref.IsHashOnly() {
|
||||||
)
|
invoice, err := db.GetInvoiceByHash(ctx, ref.PayHash()[:])
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return sqlc.Invoice{}, ErrInvoiceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise the reference may include more fields, so we'll need to
|
||||||
|
// assemble the query parameters based on the fields that are set.
|
||||||
|
var params sqlc.GetInvoiceParams
|
||||||
|
|
||||||
// Given all invoices are uniquely identified by their payment hash,
|
|
||||||
// we can use it to query a specific invoice.
|
|
||||||
if ref.PayHash() != nil {
|
if ref.PayHash() != nil {
|
||||||
params.Hash = ref.PayHash()[:]
|
params.Hash = ref.PayHash()[:]
|
||||||
}
|
}
|
||||||
@ -405,18 +417,34 @@ func fetchInvoice(ctx context.Context, db SQLInvoiceQueries,
|
|||||||
} else {
|
} else {
|
||||||
rows, err = db.GetInvoice(ctx, params)
|
rows, err = db.GetInvoice(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(rows) == 0:
|
case len(rows) == 0:
|
||||||
return nil, ErrInvoiceNotFound
|
return sqlc.Invoice{}, ErrInvoiceNotFound
|
||||||
|
|
||||||
case len(rows) > 1:
|
case len(rows) > 1:
|
||||||
// In case the reference is ambiguous, meaning it matches more
|
// In case the reference is ambiguous, meaning it matches more
|
||||||
// than one invoice, we'll return an error.
|
// than one invoice, we'll return an error.
|
||||||
return nil, fmt.Errorf("ambiguous invoice ref: %s: %s",
|
return sqlc.Invoice{}, fmt.Errorf("ambiguous invoice ref: "+
|
||||||
ref.String(), spew.Sdump(rows))
|
"%s: %s", ref.String(), spew.Sdump(rows))
|
||||||
|
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return nil, fmt.Errorf("unable to fetch invoice: %w", err)
|
return sqlc.Invoice{}, fmt.Errorf("unable to fetch invoice: %w",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchInvoice fetches the common invoice data and the AMP state for the
|
||||||
|
// invoice with the given reference.
|
||||||
|
func fetchInvoice(ctx context.Context, db SQLInvoiceQueries, ref InvoiceRef) (
|
||||||
|
*Invoice, error) {
|
||||||
|
|
||||||
|
// Fetch the invoice from the database.
|
||||||
|
sqlInvoice, err := getInvoiceByRef(ctx, db, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -433,8 +461,8 @@ func fetchInvoice(ctx context.Context, db SQLInvoiceQueries,
|
|||||||
fetchAmpHtlcs = true
|
fetchAmpHtlcs = true
|
||||||
|
|
||||||
case HtlcSetOnlyModifier:
|
case HtlcSetOnlyModifier:
|
||||||
// In this case we'll fetch all AMP HTLCs for the
|
// In this case we'll fetch all AMP HTLCs for the specified set
|
||||||
// specified set id.
|
// id.
|
||||||
if ref.SetID() == nil {
|
if ref.SetID() == nil {
|
||||||
return nil, fmt.Errorf("set ID is required to use " +
|
return nil, fmt.Errorf("set ID is required to use " +
|
||||||
"the HTLC set only modifier")
|
"the HTLC set only modifier")
|
||||||
@ -454,8 +482,8 @@ func fetchInvoice(ctx context.Context, db SQLInvoiceQueries,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the rest of the invoice data and fill the invoice struct.
|
// Fetch the rest of the invoice data and fill the invoice struct.
|
||||||
_, invoice, err = fetchInvoiceData(
|
_, invoice, err := fetchInvoiceData(
|
||||||
ctx, db, rows[0], setID, fetchAmpHtlcs,
|
ctx, db, sqlInvoice, setID, fetchAmpHtlcs,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -658,7 +686,7 @@ func fetchAmpState(ctx context.Context, db SQLInvoiceQueries, invoiceID int64,
|
|||||||
|
|
||||||
invoiceKeys[key] = struct{}{}
|
invoiceKeys[key] = struct{}{}
|
||||||
|
|
||||||
if htlc.State != HtlcStateCanceled { //nolint: ll
|
if htlc.State != HtlcStateCanceled {
|
||||||
amtPaid += htlc.Amt
|
amtPaid += htlc.Amt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,6 +255,38 @@ func (q *Queries) GetInvoice(ctx context.Context, arg GetInvoiceParams) ([]Invoi
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getInvoiceByHash = `-- name: GetInvoiceByHash :one
|
||||||
|
SELECT i.id, i.hash, i.preimage, i.settle_index, i.settled_at, i.memo, i.amount_msat, i.cltv_delta, i.expiry, i.payment_addr, i.payment_request, i.payment_request_hash, i.state, i.amount_paid_msat, i.is_amp, i.is_hodl, i.is_keysend, i.created_at
|
||||||
|
FROM invoices i
|
||||||
|
WHERE i.hash = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetInvoiceByHash(ctx context.Context, hash []byte) (Invoice, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getInvoiceByHash, hash)
|
||||||
|
var i Invoice
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Hash,
|
||||||
|
&i.Preimage,
|
||||||
|
&i.SettleIndex,
|
||||||
|
&i.SettledAt,
|
||||||
|
&i.Memo,
|
||||||
|
&i.AmountMsat,
|
||||||
|
&i.CltvDelta,
|
||||||
|
&i.Expiry,
|
||||||
|
&i.PaymentAddr,
|
||||||
|
&i.PaymentRequest,
|
||||||
|
&i.PaymentRequestHash,
|
||||||
|
&i.State,
|
||||||
|
&i.AmountPaidMsat,
|
||||||
|
&i.IsAmp,
|
||||||
|
&i.IsHodl,
|
||||||
|
&i.IsKeysend,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const getInvoiceBySetID = `-- name: GetInvoiceBySetID :many
|
const getInvoiceBySetID = `-- name: GetInvoiceBySetID :many
|
||||||
SELECT i.id, i.hash, i.preimage, i.settle_index, i.settled_at, i.memo, i.amount_msat, i.cltv_delta, i.expiry, i.payment_addr, i.payment_request, i.payment_request_hash, i.state, i.amount_paid_msat, i.is_amp, i.is_hodl, i.is_keysend, i.created_at
|
SELECT i.id, i.hash, i.preimage, i.settle_index, i.settled_at, i.memo, i.amount_msat, i.cltv_delta, i.expiry, i.payment_addr, i.payment_request, i.payment_request_hash, i.state, i.amount_paid_msat, i.is_amp, i.is_hodl, i.is_keysend, i.created_at
|
||||||
FROM invoices i
|
FROM invoices i
|
||||||
|
@ -24,6 +24,7 @@ type Querier interface {
|
|||||||
// from different invoices. It is the caller's responsibility to ensure that
|
// from different invoices. It is the caller's responsibility to ensure that
|
||||||
// we bubble up an error in those cases.
|
// we bubble up an error in those cases.
|
||||||
GetInvoice(ctx context.Context, arg GetInvoiceParams) ([]Invoice, error)
|
GetInvoice(ctx context.Context, arg GetInvoiceParams) ([]Invoice, error)
|
||||||
|
GetInvoiceByHash(ctx context.Context, hash []byte) (Invoice, error)
|
||||||
GetInvoiceBySetID(ctx context.Context, setID []byte) ([]Invoice, error)
|
GetInvoiceBySetID(ctx context.Context, setID []byte) ([]Invoice, error)
|
||||||
GetInvoiceFeatures(ctx context.Context, invoiceID int64) ([]InvoiceFeature, error)
|
GetInvoiceFeatures(ctx context.Context, invoiceID int64) ([]InvoiceFeature, error)
|
||||||
GetInvoiceHTLCCustomRecords(ctx context.Context, invoiceID int64) ([]GetInvoiceHTLCCustomRecordsRow, error)
|
GetInvoiceHTLCCustomRecords(ctx context.Context, invoiceID int64) ([]GetInvoiceHTLCCustomRecordsRow, error)
|
||||||
|
@ -54,6 +54,11 @@ WHERE (
|
|||||||
GROUP BY i.id
|
GROUP BY i.id
|
||||||
LIMIT 2;
|
LIMIT 2;
|
||||||
|
|
||||||
|
-- name: GetInvoiceByHash :one
|
||||||
|
SELECT i.*
|
||||||
|
FROM invoices i
|
||||||
|
WHERE i.hash = $1;
|
||||||
|
|
||||||
-- name: GetInvoiceBySetID :many
|
-- name: GetInvoiceBySetID :many
|
||||||
SELECT i.*
|
SELECT i.*
|
||||||
FROM invoices i
|
FROM invoices i
|
||||||
|
Loading…
x
Reference in New Issue
Block a user