Skip to content

feat(indexer): push result cap and ordering into KV block search (PLT-748)#3689

Open
amir-deris wants to merge 5 commits into
mainfrom
amir/plt-748-bound-kv-indexer
Open

feat(indexer): push result cap and ordering into KV block search (PLT-748)#3689
amir-deris wants to merge 5 commits into
mainfrom
amir/plt-748-bound-kv-indexer

Conversation

@amir-deris

@amir-deris amir-deris commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

BlockSearch/TxSearch previously fetched the entire match set from the KV
indexer, sorted it, and only then applied MaxTxSearchResults at the RPC layer.
For broad queries this materializes and sorts far more heights than the caller
will ever see. This PR pushes the result cap and ordering down into the
indexer
so a broad block query is bounded at the scan path.

What changed

  • New indexer.SearchOptions{Limit, OrderDesc} threaded through the
    TxIndexer / BlockIndexer interfaces, the EventSink interface, the KV
    sink, and the null/mock implementations.
  • KV block indexer (block/kv) — bounded search:
    • Fast path (planBounded / searchBounded): when every condition is an
      equality (point-probeable) or a block.height range (evaluable from the
      candidate height), drive a single height-ordered scan in order_by order,
      point-probe the remaining conditions per candidate, and stop at Limit.
      Memory is bounded by results kept, not by total match cardinality.
    • Fallback path (intersect + collectBounded): queries with
      CONTAINS/MATCHES/EXISTS or non-height ranges can't be point-probed, so
      the intersection is materialized as before, then ordered and capped.
  • RPC BlockSearch: validates order_by up front, passes it and the cap
    into the indexer, and keeps the post-sort cap as a safety net for sinks that
    ignore the limit.
  • RPC TxSearch: passes SearchOptions through now, but the tx scan path is
    not yet bounded — marked with TODO(PLT-748); behavior is unchanged for tx.

Tests

  • TestBlockIndexerBounded covers the fast path (equality driver, multi-equality
    probe, equality + height range, pure height-range driver) and the fallback
    path (CONTAINS), across asc/desc and bounded/unbounded limits.
  • Existing indexer/sink/rpc tests updated for the new SearchOptions argument.

Follow-up

  • Bound the KV tx scan path the same way (TODO(PLT-748) in tx.go).

PLT-748

@cursor

cursor Bot commented Jul 1, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Block search correctness and RPC result ordering/caps change on a hot path; interface is widely threaded but tx behavior is intentionally unchanged pending PLT-748.

Overview
Introduces indexer.SearchOptions (Limit, OrderDesc) on block/tx indexer and EventSink search APIs so RPC can pass MaxTxSearchResults and order_by into the store instead of loading every match first.

KV block search gains a bounded fast path: for queries made only of equalities and block.height ranges, it scans one height-ordered prefix (asc/desc), point-checks other conditions, and stops at the limit. CONTAINS / MATCHES / EXISTS and other non-height ranges still use full intersection, then collectBounded sorts and truncates. BlockSearch validates order_by up front, forwards options to the sink, and keeps RPC sort plus cap as a safety net.

Tx search now passes SearchOptions through the stack, but the KV tx indexer still ignores them (noted with TODO(PLT-748)); sort and cap at RPC are unchanged. Mocks, null/psql sinks, and TestBlockIndexerBounded were updated for the new signature and bounded behavior.

Reviewed by Cursor Bugbot for commit eaa58f8. Bugbot is set up for automated code reviews on this repo. Configure here.

@amir-deris amir-deris changed the title Pushed limit/order block search into indexer, added tests feat(indexer): push result cap and ordering into KV block search (PLT-748) Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJul 1, 2026, 8:20 PM

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.31638% with 49 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.36%. Comparing base (7529e2e) to head (eaa58f8).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...i-tendermint/internal/state/indexer/block/kv/kv.go 75.47% 14 Missing and 12 partials ⚠️
...dermint/internal/state/indexer/mocks/event_sink.go 25.00% 6 Missing and 6 partials ⚠️
sei-tendermint/internal/rpc/core/blocks.go 68.75% 5 Missing ⚠️
...tendermint/internal/state/indexer/block/kv/util.go 90.90% 2 Missing ⚠️
...endermint/internal/state/indexer/sink/psql/psql.go 0.00% 2 Missing ⚠️
...ndermint/internal/state/indexer/block/null/null.go 0.00% 1 Missing ⚠️
...-tendermint/internal/state/indexer/tx/null/null.go 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3689      +/-   ##
==========================================
- Coverage   59.29%   58.36%   -0.93%     
==========================================
  Files        2272     2185      -87     
  Lines      188193   178553    -9640     
==========================================
- Hits       111580   104207    -7373     
+ Misses      66560    65085    -1475     
+ Partials    10053     9261     -792     
Flag Coverage Δ
sei-chain-pr 43.43% <72.31%> (-21.55%) ⬇️
sei-db 70.41% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-tendermint/internal/rpc/core/tx.go 69.73% <100.00%> (+1.24%) ⬆️
sei-tendermint/internal/state/indexer/indexer.go 100.00% <ø> (ø)
...ei-tendermint/internal/state/indexer/sink/kv/kv.go 100.00% <100.00%> (ø)
...endermint/internal/state/indexer/sink/null/null.go 100.00% <100.00%> (ø)
sei-tendermint/internal/state/indexer/tx/kv/kv.go 66.21% <100.00%> (ø)
...ndermint/internal/state/indexer/block/null/null.go 0.00% <0.00%> (ø)
...-tendermint/internal/state/indexer/tx/null/null.go 0.00% <0.00%> (ø)
...tendermint/internal/state/indexer/block/kv/util.go 86.66% <90.90%> (+1.76%) ⬆️
...endermint/internal/state/indexer/sink/psql/psql.go 2.54% <0.00%> (ø)
sei-tendermint/internal/rpc/core/blocks.go 66.09% <68.75%> (+0.42%) ⬆️
... and 2 more

... and 87 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A focused, well-documented refactor that pushes the result cap and ordering into the KV block indexer's scan path; the new fast/fallback paths faithfully reproduce the legacy match semantics and are well covered by tests. No correctness or security issues found — only minor non-blocking notes.

Findings: 0 blocking | 5 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Second-opinion passes: Codex reported no material findings; the Cursor review file (cursor-review.md) is empty (no output produced). Noting per review instructions.
  • Test coverage gap: TestBlockIndexerBounded covers equality drivers and a single lower-bound height range (desc), but not (a) a pure block.height-range driver in ascending order, (b) a dual-bounded range (block.height >= x AND block.height <= y), or (c) context-cancellation returning partial results in the bounded path. The logic appears correct for all three; adding cases would lock the behavior in.
  • Minor perf: the pure block.height-range driver scans from the height extremum instead of seeking to the range boundary, so a bounded query like block.height <= K on a tall chain still iterates every height above K (cheap range-reject, no point-probe). This matches the old O(total) cost, so it's not a regression — a future optimization could seek the iterator to the bound.
  • The seen dedup map in searchBounded is effectively defensive: block event keys are unique per (compositeKey, eventValue, height) because the type suffix is always "finalize_block", so a height cannot repeat within a single driver scan today. Fine to keep for robustness; just noting it is not exercised.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread sei-tendermint/internal/rpc/core/tx.go Outdated
// and ordering down to the scan.
results, err := sink.SearchTxEvents(ctx, q, indexer.SearchOptions{
Limit: env.Config.MaxTxSearchResults,
OrderDesc: req.OrderBy != "asc",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Nit: OrderDesc: req.OrderBy != "asc" maps any invalid order_by (e.g. "foo") to desc. Harmless today because the tx indexer ignores opts entirely and the switch below (lines 83-100) still validates order_by and returns ErrInvalidRequest. Worth keeping the TODO(PLT-748) note in mind: once the tx scan path honors these opts, order_by should be validated before it's turned into OrderDesc so a bad value can't silently drive the scan.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR pushes the result cap and ordering down into the KV block indexer via a new bounded/point-probe fast path, with a materialize-then-cap fallback. The change is correct, consistent across all interface implementations and callers, and well tested; only minor non-blocking observations.

Findings: 0 blocking | 5 non-blocking | 2 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Cursor's second-opinion review (cursor-review.md) is empty — that pass produced no output. Codex reported no material findings but noted it could not run tests (no Go 1.25.6 / no network in its sandbox), so its pass was static-only.
  • Performance (acknowledged as future work via TODO): the height-range driver scans the entire block.height primary-key prefix and filters candidates with heightInRange rather than seeking to the range boundary. For an ascending query with a high lower bound (e.g. block.height >= 1000000 on a long chain) this over-scans all non-matching lower heights before reaching the range. Descending queries are unaffected. Not a regression vs. the prior full-materialization behavior.
  • Test coverage suggestion: the bounded fast path is exercised, but there is no test asserting equivalence between the fast path and the fallback for the same query, nor a case where the driver equality matches only a subset of heights combined with an ascending limit. Consider adding one to guard against future divergence.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

driverEquality *syntax.Condition
// driverHeightRange, when non-nil, drives the scan off the primary
// block.height key range.
driverHeightRange *indexer.QueryRange

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] driverHeightRange is assigned in planBounded (line 422) but never read: searchBounded decides the driver purely via plan.driverEquality != nil, and candidate filtering uses plan.heightRanges. This field is effectively dead — consider removing it (and its assignment) to avoid implying it drives behavior, or actually consuming it in searchBounded for clarity.

prefix, err = orderedcode.Append(nil, plan.driverEquality.Tag, plan.driverEquality.Arg.Value())
} else {
// Drive off the primary block.height key range.
prefix, err = orderedcode.Append(nil, types.BlockHeightKey)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] nit: when driving off the primary block.height key range, the scan starts from the full-prefix boundary and relies on candidateMatches/heightInRange to discard out-of-range heights. For an ascending scan with a high lower bound this iterates many non-matching low heights before reaching the range. A future optimization could seek to the range's lower/upper bound; the TODO/doc note covers this, so non-blocking.

Comment on lines +363 to +374
// driverEquality, when non-nil, is the equality condition whose event-key
// prefix is scanned in height order. Exactly one of driverEquality /
// driverHeightRange is set.
driverEquality *syntax.Condition
// driverHeightRange, when non-nil, drives the scan off the primary
// block.height key range.
driverHeightRange *indexer.QueryRange
// equalityProbes are the remaining equality conditions, tested per
// candidate height with a point lookup.
equalityProbes []syntax.Condition
// heightRanges are block.height bounds evaluated directly from a candidate
// height. When driving off the height range it is included here too so it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The driverHeightRange *indexer.QueryRange field on boundedPlan (kv.go:369) is set by planBounded at line 422 but never read anywhere; searchBounded gates only on plan.driverEquality != nil and otherwise builds the prefix from types.BlockHeightKey directly. Behavior is correct — height-range enforcement flows through plan.heightRanges + candidateMatches/heightInRange — so this is just dead code + a slightly overstated struct comment. Suggest dropping the field (and the "drives the scan off the primary block.height key range" comment) or actually using its LowerBoundValue()/UpperBoundValue() in searchBounded to narrow the iterator bounds for narrow ranges.

Extended reasoning...

What

The fast-path plan struct declares a driverHeightRange field:

type boundedPlan struct {
    driverEquality *syntax.Condition
    // driverHeightRange, when non-nil, drives the scan off the primary
    // block.height key range.
    driverHeightRange *indexer.QueryRange
    equalityProbes []syntax.Condition
    heightRanges   []indexer.QueryRange
}

planBounded writes it in exactly one place (kv.go:422):

case len(plan.heightRanges) > 0:
    qr := plan.heightRanges[0]
    plan.driverHeightRange = &qr

A grep across the repo shows zero readers of the field — no plan.driverHeightRange anywhere except that assignment and its declaration.

Why it does not affect correctness

searchBounded at kv.go:279-284 decides how to build the driver prefix by checking plan.driverEquality:

if plan.driverEquality != nil {
    prefix, err = orderedcode.Append(nil, plan.driverEquality.Tag, plan.driverEquality.Arg.Value())
} else {
    // Drive off the primary block.height key range.
    prefix, err = orderedcode.Append(nil, types.BlockHeightKey)
}

The else branch does not consult driverHeightRange — it just builds a prefix over the full block.height keyspace. Range enforcement is done elsewhere: heightRanges (the slice, not the driver pointer) is walked in candidateMatches, and heightInRange filters each candidate. The field is redundant with the fact that driverEquality == nil already implies the height-range driver was chosen.

Step-by-step proof (query: block.height >= 6, desc, limit 3)

  1. planBounded sees one range on block.height, no equalities.
  2. It falls into the case len(plan.heightRanges) > 0 branch and assigns plan.driverHeightRange = &qr.
  3. searchBounded is called. plan.driverEquality is nil, so the else branch runs: prefix = orderedcode.Append(nil, types.BlockHeightKey). The driverHeightRange field is never touched.
  4. The iterator scans all indexed block.height primary keys in descending order.
  5. For each candidate height, candidateMatches calls heightInRange(h, plan.heightRanges[i]), which correctly rejects h < 6.
  6. After collecting 3 matches, the loop exits.

Result: correct ([10, 9, 8]), but the field played no role. It could be removed with no observable behavior change.

Missed optimization

The struct comment claims the field "drives the scan off the primary block.height key range", which implies the range bounds are used to narrow the iterator. They are not. A query like block.height BETWEEN 100 AND 200 on a chain with millions of blocks still creates an iterator over the entire block.height prefix and filters via heightInRange. If searchBounded consulted driverHeightRange to build a bounded iterator with LowerBoundValue()/UpperBoundValue(), that would be a real perf win — but that is a follow-up, not a regression from this PR.

Fix

Either drop the field and update the accompanying comment (simplest, immediately reduces cognitive load) or wire it into searchBounded to narrow the iterator range. Both are acceptable; the former is safer for this PR since the latter needs its own tests. Not merge-blocking — the code works as intended.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well-structured, well-tested change that threads SearchOptions{Limit, OrderDesc} through the indexer interfaces and adds a bounded/ordered fast path to the KV block indexer; correctness and ordering look sound. No blockers — only a performance-completeness gap (the pure height-range driver isn't truly bounded for far-side bounds) and a minor dead field.

Findings: 0 blocking | 4 non-blocking | 2 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Codex's one finding is valid and non-blocking: the pure block.height-range fast path (searchBounded driving off types.BlockHeightKey) always starts at the full height prefix, so block.height <= 100 with order_by=desc (or block.height >= <large> with order_by=asc) still traverses the entire opposite tail before it starts collecting matches. This is no worse than the pre-PR full-materialization behavior and is fine for the common near-side/most-recent case, but the PR's stated goal of bounding the scan isn't achieved here. Consider seeking the iterator to the range bound (the reverse iterator could start at the upper bound; the forward iterator at the lower bound). Cursor's review file (cursor-review.md) and REVIEW_GUIDELINES.md were both empty, so no additional input from those.
  • TxSearch now passes SearchOptions through but the tx KV indexer ignores them (TODO(PLT-748)); behavior is intentionally unchanged for tx and clearly marked — no action needed, just confirming the double-cap (indexer + RPC safety net) is intentional.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

prefix, err = orderedcode.Append(nil, plan.driverEquality.Tag, plan.driverEquality.Arg.Value())
} else {
// Drive off the primary block.height key range.
prefix, err = orderedcode.Append(nil, types.BlockHeightKey)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The pure height-range driver always starts iterating from the full block.height prefix, so it isn't actually bounded when the bound is on the far side of the scan direction (e.g. block.height <= 100 with order_by=desc, or block.height >= <large> with order_by=asc) — it traverses every height above/below the bound before collecting any result. You already have the bound in plan.heightRanges/plan.driverHeightRange; seeking the iterator to that bound (reverse iterator starting at the upper bound, forward at the lower bound) would make this path genuinely bounded. Not a regression vs. the prior behavior, but worth addressing to meet the PR's stated goal.

// heightRanges has exactly one element here, which also filters
// candidates during the scan.
qr := plan.heightRanges[0]
plan.driverHeightRange = &qr

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] plan.driverHeightRange is assigned here but never read anywhere — searchBounded only checks plan.driverEquality != nil and otherwise drives off types.BlockHeightKey, and the range filtering happens via plan.heightRanges in candidateMatches. Either drop this field or use it (e.g. to seek the iterator to the bound as noted above).

Comment on lines +81 to 87
func (idx *BlockerIndexer) Search(ctx context.Context, q *query.Query, opts indexer.SearchOptions) ([]int64, error) {
results := make([]int64, 0)
select {
case <-ctx.Done():
if ctx.Err() != nil {
return results, nil

default:
}

conditions := q.Syntax()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 [pre-existing] Search short-circuits on any block.height = X equality via lookForHeight and returns []int64{H} after only checking idx.Has(H) — any additional AND-conditions in the query are silently ignored. For example, block.height = 5 AND app.name = 'sei' returns [5] if height 5 is indexed even when app.name = 'sei' was never emitted on that block, producing a false positive from BlockSearch. This is inherited from upstream Tendermint (present pre-PR at kv.go:91-103) and the PR does not modify the short-circuit branch, but the enclosing Search function is substantially reorganized here — flagging for the author to decide whether to fix in this PR or as a follow-up (a proper fix likely needs to treat block.height = X as a probe/filter rather than a driver, since naive removal breaks the standalone height case).

Extended reasoning...

What

In BlockerIndexer.Search at sei-tendermint/internal/state/indexer/block/kv/kv.go:87-101:

height, ok := lookForHeight(conditions)
if ok {
    ok, err := idx.Has(height)
    if err != nil {
        return nil, err
    }
    if ok {
        return []int64{height}, nil
    }
    return results, nil
}

lookForHeight (util.go:138) iterates conditions and returns (H, true) on the first block.height = H (TEq) it finds — it does not require the query to consist of only that condition. Search then confirms the height is indexed via idx.Has(H) and returns []int64{H}. Has only checks the primary block.height key store; it does not evaluate any of the other conditions in the query.

Why existing code does not prevent it

The short-circuit runs before both the fast-path planBounded/searchBounded and the fallback intersect/collectBounded, so the other conditions are never applied by the indexer. On the RPC side, BlockSearch in sei-tendermint/internal/rpc/core/blocks.go does not re-verify conditions against the returned heights — it just paginates and returns them.

The function comment even documents this behavior: "In the case of height queries, i.e. block.height=H, if the height is indexed, that height alone will be returned." That wording made sense when height-equality queries were expected to appear alone, but nothing today enforces that.

Step-by-step proof

Using the existing TestBlockIndexer fixture (heights 1..11 all carry finalize_event1.proposer=FCAA001; only even heights carry finalize_event2.foo):

  1. Query: block.height = 5 AND finalize_event2.foo <= 5. Height 5 has no indexed foo event.
  2. q.Syntax() yields [{block.height, TEq, 5}, {finalize_event2.foo, TLeq, 5}].
  3. lookForHeight walks the slice, hits condition 0 with Tag=block.height, Op=TEq, returns (5, true) — condition 1 is never inspected.
  4. idx.Has(5) reads the primary key orderedcode(BlockHeightKey, 5) from the store — it exists (height 5 was indexed), so returns true.
  5. Search returns []int64{5}.

Expected under AND semantics: []int64{}, because height 5 does not satisfy finalize_event2.foo <= 5. The caller receives a false positive.

Impact

BlockSearch RPC callers using compound block.height = X AND ... queries get incorrect results — they see block 5 when the events they filtered on are not actually present. Callers that trust the RPC response (block explorers, monitoring, most indexers) will silently mis-attribute events to the wrong block or count blocks that should have been filtered out.

Why pre-existing

The short-circuit at kv.go:91-103 is preserved verbatim from b8776ed (pre-PR). This PR restructures the surrounding Search function — extracting intersect, adding planBounded/searchBounded/collectBounded — but does not touch the lookForHeight early-return itself. The bug is inherited from upstream Tendermint. All four verifiers converged on pre_existing.

How to fix

A naive fix (deleting the short-circuit) would break the standalone block.height = X case: block.height is stored in the primary index under orderedcode(BlockHeightKey, int64(H)), while planBounded would build the driver prefix from c.Arg.Value() (the string "5"), producing a mismatched key layout. A proper fix likely treats block.height = X as an equalityProbe/filter over the height range rather than a driver, or short-circuits only when conditions has length 1. This is real behavior work on a widely-used RPC and probably belongs in a follow-up rather than blocking this scan-bounding PR.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR cleanly threads a new SearchOptions{Limit, OrderDesc} through the indexer/sink/RPC stack and adds a well-tested bounded fast path for the KV block indexer; correctness looks sound. The main concern is a scan-efficiency gap in the height-range driver (raised by Codex) plus minor cleanups.

Findings: 0 blocking | 7 non-blocking | 2 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Second-opinion inputs: cursor-review.md and REVIEW_GUIDELINES.md are both empty (no Cursor findings and no repo-specific guidelines to apply). Codex produced a single finding, incorporated below.
  • No prompt-injection or suspicious instructions were found in the PR title, description, or diff.
  • TxSearch (tx.go) now passes SearchOptions but the tx KV indexer ignores it and the cap is still applied after the full sort — this is explicitly acknowledged with TODO(PLT-748) and behavior is unchanged, so it is fine as a follow-up.
  • The fast path correctly relies on lookForHeight short-circuiting any block.height = H equality before planBounded runs, so a driver equality is never a block.height point key (which is stored int64-encoded, not string-encoded). Worth a brief test asserting a mixed query like block.height = H AND app.name = 'x' still routes as expected, since that behavior is load-bearing for the new path.
  • Consider a targeted benchmark comparing the new bounded path vs. the old materialize-and-sort path for a broad app.name = 'x' query to document the win claimed in the PR description.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

prefix, err = orderedcode.Append(nil, plan.driverEquality.Tag, plan.driverEquality.Arg.Value())
} else {
// Drive off the primary block.height key range.
prefix, err = orderedcode.Append(nil, types.BlockHeightKey)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The height-range driver seeds the iterator from the full block.height prefix and never seeks to the actual range bounds — the bounds are only enforced afterward via heightInRange in candidateMatches. For a one-sided range whose retained end is far from the scan start (e.g. block.height < 1000 with order_by=desc on a tall chain), the scan walks from the tip down through millions of out-of-range heights before it reaches the matching region. The same applies to an equality driver combined with a restrictive height range in the opposite direction. This is a scan-work regression versus the old matchRange, which seeded the iterator at the range bound; memory stays bounded but the scan does not. Consider narrowing the driver prefix/iterator domain using the range bound for the scan direction (upper bound for desc, lower bound for asc).

driverEquality *syntax.Condition
// driverHeightRange, when non-nil, drives the scan off the primary
// block.height key range.
driverHeightRange *indexer.QueryRange

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] nit: driverHeightRange is assigned in planBounded but never read — searchBounded only branches on driverEquality != nil and the range filtering goes through plan.heightRanges. Either drop the field or use it in searchBounded (it would be the natural place to compute the range-bounded start key mentioned above).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant