Skip to content

logdb: evaluate narrow topic0-only event filters by seq range scan - #1693

Merged
libotony merged 2 commits into
masterfrom
tony/logdb-topic0-scan
Oct 29, 2026
Merged

libotony merged 2 commits into
masterfrom
tony/logdb-topic0-scan

Conversation

@libotony

Copy link
Copy Markdown
Member

Description

POST /logs/event with a narrow block range and topic0-only criteria (no address, no topic1..4) runs in time proportional to the chain-wide count of that topic0, not to the block range. The subquery is planned as SEARCH event USING COVERING INDEX event_i1 (topic0=?) + USE TEMP B-TREE FOR ORDER BY: event_i1 is (topic0, address), and with address unconstrained the seq bounds cannot enter the index, so every entry for that topic0 is read and filtered. On a mainnet logs-v2.db (797M events) a single-block Transfer query takes ~110 s regardless of the range.

This PR changes only the generated SQL. For a criterion that constrains nothing but topic0, and only when the requested span is at most 100,000 blocks, the term is rendered as +topic0 = …. The unary plus stops SQLite from using the term for index lookup, so the only remaining constraint is the seq range (the rowid) and the plan becomes SEARCH event USING INTEGER PRIMARY KEY (rowid>? AND rowid<?): cost proportional to the rows in the span, no sort, early LIMIT stop. Every other criteria shape, and topic0-only queries over wider spans or with no range, keep their current plan.

Measured on the same mainnet DB (external SSD, sqlite3 3.51):

query before after
6 topic0 criteria, single block (production incident request) 117 s 3 ms
Transfer, single block / last 10k / last 100k blocks 109–113 s 2–7 ms
rare topic0 (4.9k rows chain-wide), single block 7 ms <1 ms
rare topic0, 100k blocks (worst case within the bound) ~1 ms ~1 s cold / 40 ms warm

The 100,000-block bound keeps the worst case (rare topic0, cold page cache) an order of magnitude under the 10 s API timeout; at 1,000,000 blocks the same case measured 9.8 s cold. Wider topic0-only queries are unchanged and still slow; fixing those needs an index on (topic0), which #1690 proposes and which is a separate decision. The plan-pinning test here is the one proposed in #1690's review, with expectations for the current schema, so it carries over if that index lands later.

Mixed criteria sets that contain a topic0-only criterion also take the range scan within the bound (SQLite drops the multi-index OR plan once one branch has no indexable term); this is pinned by test.

No linked issue.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • TestEventQueryPlanPerCriteriaShape: EXPLAIN QUERY PLAN pinned for 9 criteria shapes × narrow/wide/nil range, plus the 100,000-block boundary on both sides
  • TestEventFilterScanEquivalence: on a written in-memory DB, the scan path returns exactly the rows and order of the index path for topic0-only filters across ASC/DESC, ranges and offset/limit
  • Full ./logdb/... and ./api/... suites; make lint clean
  • Timings above measured against a mainnet logs-v2.db snapshot opened read-only

Test Configuration:

  • Go Version: go1.26.5 darwin/arm64
  • Hardware: Apple Silicon (macOS), mainnet DB on external SSD
  • Docker Version: N/A

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have not added any vulnerable dependencies to my code

https://claude.ai/code/session_01EQzJUxb99n2LVoqvi7ioAx

@codecov-commenter

codecov-commenter commented Oct 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
logdb/logdb.go 87.09% 3 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@libotony
libotony force-pushed the tony/logdb-topic0-scan branch from 8fcc686 to b9a2f55 Compare October 26, 2026 08:47
@libotony
libotony marked this pull request as ready for review October 26, 2026 08:56
@libotony
libotony requested a review from a team as a code owner October 26, 2026 08:56
@otherview

Copy link
Copy Markdown
Member

While I read this PR, I wondered about a few edge cases, so I did some tests.

What if the request has no to?
The API then uses MaxBlockNumber, so {from: head-10} skips the scan and still takes ~110 s. #1694 uses the last block in the DB as the end of the range, for the plan decision only. The SQL keeps the requested range, so the results do not change.

What about other criteria without an address?
event_i2 to event_i5 put address before seq too. A one-block query with topic0 + topic1 reads every row for that pair in the chain. #1694 sends all address-less criteria to the scan.

Do mixed criteria sets get slower?
Not in a way we can fix. A UNION for each criterion only went from 49 ms to 35 ms, because the scan itself takes the time.

How slow is a topic0 with few events?
Over 100k blocks, it goes from ~1 ms to ~1 s with a cold cache. That is still well below the 10 s timeout, so I think it is fine. The limit counts blocks, but the cost follows events, so this number will grow with the chain. If it becomes a problem, a small COUNT probe can send these topics back to the index.

Can you run these on your mainnet logs-v2.db with #1694?

  1. Transfer topic0, {unit: block, from: <head-10>}, no to. I expect milliseconds.
  2. Transfer topic0 + a busy topic1 (a sender with many transfers), one block.
  3. A topic0 with few events over ~100k blocks, cold cache. logdb: range-scan every address-less criterion and cap open-ended spans #1694 sends more criteria to the scan, so this is the number to check against the timeout.

@moglu2017

Copy link
Copy Markdown
Contributor

I don't think we need to cover every case here. The API already has a timeout. When a user or dApp sends an unreasonable request and it times out, the right answer is for them to narrow the query conditions, or to use a centralized indexing/query service, rather than for the node to optimize every possible query shape.

@libotony
libotony merged commit b306732 into master Oct 29, 2026
37 checks passed
@libotony
libotony deleted the tony/logdb-topic0-scan branch October 29, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants