lnd icon indicating copy to clipboard operation
lnd copied to clipboard

graph: fix inefficient query for IsPublicNode

Open Abdulkbk opened this issue 2 months ago • 5 comments

fixes #10337 (partially)

Change Description

In this PR, we rewrite the query for IsPublicV1Node, which returns a boolean based on if a node has a public or private channel. This particularly fixes an issue sqlite backend as it struggles to efficiently use multiple indexes across seperate columns in one query.

Step to test

cd graph/db

go test -tags test_db_sqlite  -run TestNodeIsPublic -v # ensure we still correctly distinguish between pub & priv

Abdulkbk avatar Nov 10 '25 11:11 Abdulkbk

Summary of Changes

Hello @Abdulkbk, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a significant performance bottleneck in the IsPublicV1Node query, which was particularly inefficient for sqlite databases due to its struggle with complex OR conditions across multiple indexed columns. By restructuring the query to use UNION ALL, the change optimizes how the database utilizes indexes, leading to more efficient execution and resolving the identified performance issue.

Highlights

  • Query Optimization: The IsPublicV1Node query has been rewritten to improve its efficiency, specifically targeting performance issues with the sqlite backend.
  • SQL Query Refactoring: The original query's OR condition across joined tables (n.id = c.node_id_1 OR n.id = c.node_id_2) has been refactored into two separate SELECT statements combined with UNION ALL. This change allows for better utilization of database indexes.
  • Dependency Management: A local replace directive for github.com/lightningnetwork/lnd/sqldb has been added to go.mod, pointing to the local sqldb directory.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

[^1]: Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

gemini-code-assist[bot] avatar Nov 10 '25 11:11 gemini-code-assist[bot]

can you create some stress tests to see whether union is actually better ?

ziggie1984 avatar Nov 10 '25 16:11 ziggie1984

can you create some stress tests to see whether union is actually better ?

I added a benchmark as the first commit, the following is what I found after running:

go test -tags=test_db_sqlite -bench=BenchmarkIsPublicNode  > sqlite_or.txt

# then

go test -tags=test_db_sqlite -bench=BenchmarkIsPublicNode  > sqlite_union.txt

# finally
benchstat sqlite_or.txt sqlite_union.txt 
Query Type Iterations (b.N) Time per op (ns/op)
UNION ALL 388 3,009,488 ns/op (~3ms)
OR 289 4,063,903 ns/op (~4ms)

The fact that this benchmark is run with approximately 8k nodes means that, if the number of nodes is increased to around ~80k, the difference will become more noticeable.

Abdulkbk avatar Nov 11 '25 16:11 Abdulkbk

@abdulkbk, remember to re-request review from reviewers when ready

lightninglabs-deploy avatar Dec 02 '25 17:12 lightninglabs-deploy

Thanks @MPins, for taking a look. I've addressed the feedback.

Abdulkbk avatar Dec 10 '25 10:12 Abdulkbk

Thanks for taking a look @GustavoStingelin. From the output, we can see that indeed the union_all performs better, took less time to plan and execute.

Abdulkbk avatar Dec 16 '25 19:12 Abdulkbk

yeah I think having a union call makes sense, can land after the cache PR if we still see major bottlenecks, however I think the cache will already solve our problems. The speed gain with a union is probably around ~20-30% given that node ids will be equally queried meaning that in the UnionCall we will likely also have to scan the table 2 times for 50% the node announcements we receive. I think planning plans are cached in PG so the planning time is not really super critical in a query.

ziggie1984 avatar Dec 17 '25 10:12 ziggie1984