Skip to content

Remove budget-based query capping; propose against natural ceiling - #1380

Merged
DZakh merged 5 commits into
mainfrom
claude/hypersync-query-ceiling-qkqdxt
Jul 6, 2026
Merged

Remove budget-based query capping; propose against natural ceiling#1380
DZakh merged 5 commits into
mainfrom
claude/hypersync-query-ceiling-qkqdxt

Conversation

@DZakh

@DZakh DZakh commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Removes the per-chain budget mechanism from query proposal, simplifying FetchState.getNextQuery to propose queries against their natural ceiling (head block, endBlock, or mergeBlock) without buffer-size constraints. Admission against the shared buffer budget now happens exclusively in CrossChainState, which pools candidate queries from all chains and admits them in priority order until the budget is consumed.

Key Changes

  • FetchState.getNextQuery: Removed ~budget and ~chainPendingBudget parameters; no longer caps queries based on buffer fill. Queries are now proposed unconstrained by the shared buffer state.

  • Query sizing: Reduced defaultEstResponseSize from 10,000 to 5,000 items to better reflect typical partition response sizes and improve admission accuracy.

  • Buffer admission: Moved all budget enforcement to CrossChainState.checkAndFetch, which now:

    • Collects candidate queries from all chains via ChainState.getNextQuery (simplified signature)
    • Pools them and sorts by chain progress (furthest-behind first)
    • Admits queries in order until the shared budget is exhausted
    • Enforces the soft cap via itemsTarget passed to sources (HyperSync enforces server-side; RPC/Fuel/Simulate ignore it)
  • Default buffer target: Reduced from 100,000 to 50,000 items in CrossChainState.calculateTargetBufferSize.

  • Source interface: Added ~itemsTarget: int parameter to Source.t.getItemsOrThrow to communicate the soft cap from the admission loop to backends that support it (HyperSync via maxNumLogs).

  • Test updates: Removed budget-related test cases and assertions; updated expected estResponseSize values throughout to reflect the new default of 5,000.

Implementation Details

  • The maxQueryBlockNumber calculation that previously capped queries based on buffer position is removed entirely; knownHeight is used directly where needed.
  • SourceManager.executeQuery now ceils the estResponseSize estimate before passing it as itemsTarget to avoid rounding sparse partitions down to 0.
  • Comments updated to reflect that query proposal is unconstrained by the shared budget; admission happens downstream in CrossChainState.

https://claude.ai/code/session_01TPtu2dmeaji6DNdLfUHLuf

Summary by CodeRabbit

  • New Features

    • Fetch requests now better respect estimated response sizes, helping queries target a more appropriate amount of data.
    • Log-based sources can now receive an explicit maximum-log limit, improving control over large log requests.
  • Bug Fixes

    • Reduced the default buffer fallback size when no custom setting is provided.
    • Prevented very small size estimates from rounding down to zero and causing overly restrictive requests.
    • Query selection now uses natural block ceilings more consistently, improving fetch behavior across chains.

claude added 3 commits July 6, 2026 11:04
…ync/SVM maxNumLogs

getNextQuery indexed the shared buffer at a budget-derived position to cap
how far each query could range — with 100+ partitions this forced many tiny
queries per tick even though checkAndFetch's cross-chain admission loop
already gates aggregate volume by estResponseSize. Dropping the ceiling lets
each chain propose against its natural ceiling (head/endBlock/mergeBlock),
producing fewer, larger requests without changing what gets admitted.

The remaining risk was calculateEstResponseSize's flat defaultEstResponseSize
(10,000) for a partition with no query history, now paired with an unbounded
range instead of one the old ceiling had already truncated. HyperSync/SVM
already expose max_num_logs/max_num_instructions (wired through the Rust napi
layer, unused until now) as a server-enforced soft cap — pass the query's own
estResponseSize through as maxNumItems so a wrong estimate truncates the
response instead of overshooting the buffer, on every query rather than just
first-fetches. RPC keeps its own independent AIMD interval and never relied
on this ceiling; Fuel and Simulate ignore the new parameter.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPtu2dmeaji6DNdLfUHLuf
… to 5,000

Halves both the indexer-wide buffer pool default (CrossChainState.calculateTargetBufferSize)
and the flat estimate used for a partition with no query history yet
(FetchState.defaultEstResponseSize), scaling down memory pressure now that
getNextQuery no longer caps a query's range against the buffer.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPtu2dmeaji6DNdLfUHLuf
"Max" overstated it as a hard ceiling; HyperSync/SVM only treat it as a
soft target they try not to overshoot by much.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPtu2dmeaji6DNdLfUHLuf
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a9b9f8d-4d35-43ee-b1f1-93811bbe4a97

📥 Commits

Reviewing files that changed from the base of the PR and between fb2b680 and 3d850c4.

📒 Files selected for processing (5)
  • packages/envio/src/CrossChainState.res
  • packages/envio/src/FetchState.res
  • packages/envio/src/sources/SourceManager.res
  • scenarios/test_codegen/test/lib_tests/FetchState_test.res
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res
📝 Walkthrough

Walkthrough

This PR removes explicit shared-budget parameters from FetchState/ChainState/CrossChainState query-generation flows, shifting query sizing to use knownHeight and lowering default response-size/buffer values. It introduces an itemsTarget parameter across Source implementations, with HyperSync-backed sources enforcing it server-side, and updates related tests.

Changes

Budgetless query generation and itemsTarget capping

Layer / File(s) Summary
FetchState query sizing and getNextQuery refactor
packages/envio/src/FetchState.res
calculateEstResponseSize/pushQueriesForRange use knownHeight instead of maxQueryBlockNumber; getNextQuery drops budget parameters and derives candidate ranges from endBlock/mergeBlock/blockLag; defaultEstResponseSize reduced.
ChainState and CrossChainState wiring
packages/envio/src/ChainState.res(i), packages/envio/src/CrossChainState.res
ChainState.getNextQuery drops ~budget; CrossChainState.checkAndFetch calls it without ~budget=remaining; default targetBufferSize fallback lowered from 100_000 to 50_000; comments updated.
itemsTarget threaded through sources
packages/envio/src/sources/Source.res, .../SourceManager.res, .../HyperSync.res(i), .../HyperSyncSource.res, .../SvmHyperSyncSource.res, .../HyperFuelSource.res, .../RpcSource.res, .../SimulateSource.res, .../Svm.res
getItemsOrThrow gains ~itemsTarget: int; SourceManager.executeQuery derives it from estResponseSize (ceil, fallback to default); HyperSync-backed sources forward it as maxNumLogs/maxNumInstructions server-side caps; other sources ignore it.
FetchState_test.res updates
scenarios/test_codegen/test/lib_tests/FetchState_test.res
Local getNextQuery helper drops budget args; many expected queries updated to estResponseSize: 5000.; new suite validates queries proposed up to the natural ceiling.
SourceManager and other test call-site updates
scenarios/test_codegen/test/lib_tests/SourceManager_test.res, .../ClientAddressFilter_test.res, .../EventBlockFilter_test.res
fetchNext shim and call sites drop ~budget=5000; fixtures updated to estResponseSize: 5000..
itemsTarget-related test updates
scenarios/test_codegen/test/HyperSync_test.res, .../RateLimit_test.res, .../RpcSource_test.res, .../SourceBlockHashes_test.res, .../SvmHyperSyncSource_test.res, .../helpers/MockIndexer.res
Test calls and mocks updated to pass/accept ~itemsTarget/~maxNumLogs in getItemsOrThrow/query calls.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CrossChainState
  participant ChainState
  participant FetchState
  participant SourceManager
  participant Source
  CrossChainState->>ChainState: getNextQuery(cs)
  ChainState->>FetchState: getNextQuery(fetchState)
  FetchState-->>ChainState: candidate query bounded by endBlock/knownHeight
  ChainState-->>CrossChainState: candidate query
  CrossChainState->>CrossChainState: pool/admit candidates against shared budget
  CrossChainState->>SourceManager: dispatch admitted query
  SourceManager->>SourceManager: itemsTarget = ceil(query.estResponseSize)
  SourceManager->>Source: getItemsOrThrow(itemsTarget)
  Source-->>SourceManager: items capped by itemsTarget
Loading

Possibly related PRs

  • enviodev/hyperindex#1338: Removes the ~budget/~chainPendingBudget plumbing this PR strips from FetchState.getNextQuery and its ChainState/CrossChainState call sites.
  • enviodev/hyperindex#1341: Modifies the same ChainState.getNextQuery API boundary and its call-site wiring.
  • enviodev/hyperindex#1368: Changes RpcSource.res's getItemsOrThrow internals, overlapping with this PR's ~itemsTarget signature change to the same function.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: removing budget-based capping and proposing queries against the natural ceiling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
scenarios/test_codegen/test/helpers/MockIndexer.res (1)

730-741: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider capturing itemsTarget in the call payload for future test assertions.

Currently discarded via as _. Since itemsTarget is now a meaningful soft-cap signal computed upstream in SourceManager.executeQuery, capturing it (similar to fromBlock/toBlock) would let future tests assert on the computed value without further mock changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scenarios/test_codegen/test/helpers/MockIndexer.res` around lines 730 - 741,
The MockIndexer.getItemsOrThrow test helper is discarding itemsTarget, which
prevents future assertions on the soft-cap value coming from
SourceManager.executeQuery. Update the mock payload handling in getItemsOrThrow
to capture itemsTarget instead of ignoring it, following the same pattern used
for fromBlock and toBlock, so tests can inspect the computed value without
changing the mock again.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scenarios/test_codegen/test/helpers/MockIndexer.res`:
- Around line 730-741: The MockIndexer.getItemsOrThrow test helper is discarding
itemsTarget, which prevents future assertions on the soft-cap value coming from
SourceManager.executeQuery. Update the mock payload handling in getItemsOrThrow
to capture itemsTarget instead of ignoring it, following the same pattern used
for fromBlock and toBlock, so tests can inspect the computed value without
changing the mock again.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b31e0034-566f-41f1-809e-322754c2a43d

📥 Commits

Reviewing files that changed from the base of the PR and between 5195618 and fb2b680.

📒 Files selected for processing (24)
  • packages/envio/src/ChainState.res
  • packages/envio/src/ChainState.resi
  • packages/envio/src/CrossChainState.res
  • packages/envio/src/FetchState.res
  • packages/envio/src/sources/HyperFuelSource.res
  • packages/envio/src/sources/HyperSync.res
  • packages/envio/src/sources/HyperSync.resi
  • packages/envio/src/sources/HyperSyncSource.res
  • packages/envio/src/sources/RpcSource.res
  • packages/envio/src/sources/SimulateSource.res
  • packages/envio/src/sources/Source.res
  • packages/envio/src/sources/SourceManager.res
  • packages/envio/src/sources/Svm.res
  • packages/envio/src/sources/SvmHyperSyncSource.res
  • scenarios/test_codegen/test/ClientAddressFilter_test.res
  • scenarios/test_codegen/test/EventBlockFilter_test.res
  • scenarios/test_codegen/test/HyperSync_test.res
  • scenarios/test_codegen/test/RateLimit_test.res
  • scenarios/test_codegen/test/RpcSource_test.res
  • scenarios/test_codegen/test/SourceBlockHashes_test.res
  • scenarios/test_codegen/test/SvmHyperSyncSource_test.res
  • scenarios/test_codegen/test/helpers/MockIndexer.res
  • scenarios/test_codegen/test/lib_tests/FetchState_test.res
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res

calculateDefaultEstResponseSize(~partitionsCount) replaces the flat
defaultEstResponseSize constant: 20_000 / partitionsCount, clamped to
[2_000, 10_000]. With many partitions sharing the same buffer pool,
assuming every zero-history partition's first query is "big" starves the
rest of that tick's admission — so the per-partition default shrinks as
partition count grows, letting more first-queries admit concurrently while
still capping each one's actual response server-side via itemsTarget.

SourceManager's own confirmed-zero-density safety net now falls back to
FetchState.minEstResponseSize (the 2_000 floor) instead of the old flat
constant.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPtu2dmeaji6DNdLfUHLuf
Surfaces each admitted query's estResponseSize (the itemsTarget derived
from it downstream) alongside the existing fromBlock/targetBlock, rounded
for readable log output.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPtu2dmeaji6DNdLfUHLuf
@DZakh
DZakh merged commit 2fc29d1 into main Jul 6, 2026
7 of 8 checks passed
@DZakh
DZakh deleted the claude/hypersync-query-ceiling-qkqdxt branch July 6, 2026 11:47
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.

2 participants