Skip to content

feat: keys-only indexed-axis reads - #824

Merged
QuantumExplorer merged 3 commits into
developfrom
feat/indexed-axis-keys-only-reads
Aug 22, 2026
Merged

feat: keys-only indexed-axis reads#824
QuantumExplorer merged 3 commits into
developfrom
feat/indexed-axis-keys-only-reads

Conversation

@QuantumExplorer

Copy link
Copy Markdown
Member

Why

The resolving reads — indexed_<axis>_{top_k,top_k_paginated,range} — return each entry with its primary value resolved (IndexedAxisEntry, since #817). That resolution runs after the secondary page was collected, through the caller's transaction; a caller that passes None gets point reads outside the pinned iterator view the page came from. A primary deleted or rewritten by a commit in between is then reported as CorruptedData (the database is fine), or paired with a page from the older view. And a caller that only ranks — Dash Platform's ranked and having-range executors project every entry to key_pair() — pays up to k primary reads for values it discards and inherits that window for nothing. Surfaced by review on dashpay/platform#4382.

What

Nine keys-only entry points:

indexed_{count,sum,avg}_top_k_keys            -> Vec<(T, Vec<u8>)>
indexed_{count,sum,avg}_top_k_paginated_keys  -> IndexedTopKKeysPage<T> { entries: Vec<(T, Vec<u8>)>, skipped }
indexed_{count,sum,avg}_range_keys            -> Vec<(T, Vec<u8>)>

They return the (ordering_value, original_key) pairs straight from the secondary view and never open the primary.

Structure: each read shape now has one _rows_generic core carrying all of the pinned-view logic (unchanged — the diff in those bodies is only the removal of the resolve call and the TxRef creation, which moves to the wrappers), and two thin wrappers on top: the resolving one (existing behaviour, existing signatures) and the keys-only one. Both are built on the same core, so they cannot disagree about the page. The per-axis inclusive-bound encodings used by the range reads are factored into count_range_bounds / sum_range_bounds / avg_range_bounds, shared by both variants.

No change to any existing signature or behaviour; IndexedTopKKeysPage is exported next to IndexedTopKPage.

Tests

indexed_axis_keys_only_read_tests: the paginated keys-only page equals the resolving page projected with key_pairs() — entries and skipped — on every axis, both directions, with and without an offset; range and plain top-k agree likewise; keys-only reads cost strictly fewer seeks and loaded bytes than their resolving counterparts; inverted bounds stay the empty answer. Full grovedb lib suite green, clippy clean, verifier-only build unaffected.

🤖 Generated with Claude Code

The resolving reads (indexed_<axis>_{top_k,top_k_paginated,range})
return each entry with its primary value resolved. That resolution
happens after the secondary page was collected, through the caller's
transaction — and a caller that passes None gets point reads outside
the pinned iterator view the page came from, so a primary deleted or
rewritten by a commit in between is reported as corruption or paired
with a page from the older view. Callers that only rank pay up to k
primary reads for values they discard and inherit that window for
nothing (dashpay/platform#4382 review).

Add indexed_{count,sum,avg}_{top_k,top_k_paginated,range}_keys, which
return the (value, original_key) pairs straight from the secondary view
and never open the primary, plus IndexedTopKKeysPage for the paginated
shape. Each read shape now has one _rows_generic core holding all the
pinned-view logic; the resolving wrapper and the keys-only wrapper are
both built on it, so the two cannot disagree on the page. The per-axis
range bound encodings are factored into shared helpers.

Tests: keys-only pages equal the resolving pages projected to their
ranking pairs (including the skipped count) on every axis, both
directions, with and without an offset; range and plain top-k agree
likewise; keys-only reads cost strictly fewer seeks and loaded bytes;
inverted bounds stay the empty answer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@QuantumExplorer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd23c1ac-0c46-4aaa-9f34-66f57097d2bf

📥 Commits

Reviewing files that changed from the base of the PR and between fe17045 and ee4cee3.

📒 Files selected for processing (4)
  • grovedb/src/lib.rs
  • grovedb/src/operations/indexed_tree.rs
  • grovedb/src/tests/indexed_axis_keys_only_read_tests.rs
  • grovedb/src/tests/mod.rs
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/indexed-axis-keys-only-reads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.29730% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.39%. Comparing base (fe17045) to head (ee4cee3).
⚠️ Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
grovedb/src/operations/indexed_tree.rs 97.29% 11 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #824      +/-   ##
===========================================
+ Coverage    92.34%   92.39%   +0.04%     
===========================================
  Files          285      288       +3     
  Lines        87119    87906     +787     
===========================================
+ Hits         80449    81219     +770     
- Misses        6670     6687      +17     
Components Coverage Δ
grovedb-core 90.58% <97.29%> (+0.07%) ⬆️
merk 93.27% <ø> (ø)
storage 87.08% <ø> (+0.03%) ⬆️
commitment-tree 96.29% <ø> (+0.22%) ⬆️
mmr 96.49% <ø> (+0.07%) ⬆️
bulk-append-tree 92.27% <ø> (+1.06%) ⬆️
element 97.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

QuantumExplorer and others added 2 commits August 22, 2026 14:29
The keys-only wrappers were inserted between the doc comment and the
function it documents, which newer clippy flags as
empty_line_after_doc_comments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ction

Same insertion slip as the previous commit, at the range-bound helpers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit 1b18de6 into develop Aug 22, 2026
11 checks passed
@QuantumExplorer
QuantumExplorer deleted the feat/indexed-axis-keys-only-reads branch August 22, 2026 07:36
QuantumExplorer added a commit that referenced this pull request Aug 22, 2026
)

run_path_query served every ReadMode::Axis read through the resolving
indexed-axis reads, so the unified path had no way to ask for the
ranking pairs alone: a caller that only ranks paid up to k primary
point reads per page — through its own transaction, after the pinned
secondary page was collected, so outside the iterator's view when that
transaction is None — for values it discards. #824 gave the standalone
API a keys-only shape; this gives the PathQuery vocabulary the same.

AxisQuery gains `projection: AxisProjection { Entries (default), Keys }`,
encoded as a trailing frozen tag byte; `validate` rejects Keys on the
traversals that list no entries (rank-of-key, value-range aggregates).
run_path_query routes a Keys read through the _keys reads and returns
AxisKeys / BranchedAxisKeys (absent branches None, as for entries);
AxisEntries::to_keys is the projection. The projection is an
unproved-read choice: a proof always carries the values and
verification yields entries, so prover and verifier treat Keys exactly
as Entries — a keys read is a strict projection of the verified page,
which a test pins. PathQuery::new_axis(path, axis_query) builds a read
from an already-configured AxisQuery.

Tests: keys == entries projected on every axis, both directions, with
and without an offset, for ranked-page and bounded traversals; branched
keys == branched entries projected including absent branches; keys ==
verified entries projected; fewer seeks and loaded bytes; rejection on
non-listing traversals; projection round-trips with a frozen tag and an
unknown tag is rejected.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
QuantumExplorer added a commit to dashpay/platform that referenced this pull request Aug 22, 2026
…is reads

The ranked and having-range executors only rank — every entry was
projected to its (value, key) pair and the resolved primary value
discarded — yet the resolving reads paid up to k primary point reads
per page through the caller's transaction, outside the pinned iterator
view the page came from, so a primary deleted or rewritten by a commit
in between could surface as CorruptedData or pair a newer primary with
an older page. grovedb's keys-only reads (dashpay/grovedb#824,
indexed_*_top_k_paginated_keys / indexed_*_range_keys) return the
ranking pairs straight from the secondary view and never open the
primary; the executors and the e2e test helpers use them.

grovedb pinned to develop head f52adb64, which also carries the
append-only storage accounting (dashpay/grovedb#822 via #825/#826:
write churn reported as replaced bytes, each note's permanent bytes
charged once) and the keys-only projection on the unified PathQuery
(#827). The genesis shielded seeding test passes the grove version to
the commitment tree's save/commit_mmr, which now take it.

The two shielded fee-floor tests (flat fee >= estimated write cost)
remain red at this pin: the estimate for a 1-action transfer is now
~397M credits (storage 133M + processing 264M, the epoch's replaced
bytes at the processing rate) against the 131M flat fee — down from
18.5B at the previous pin, and the subject of the shielded pricing
change that follows this PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant