Skip to content

Prefix-level rankedCountable: place a CountIndexedTree at a non-terminal index level to rank groups by subtree count #4529

Description

@PastaPastaPasta

Problem

The v14 ranked machinery groups at exactly one level: the deepest prefix property of an index, ranked by direct member count. Ranking one level higher — groups by their whole-subtree totals — is not expressible, and it blocks two natural features on the indexOnly-likes shape this repo itself ships as its reference fixture (yappr-likes-contract.json):

  • Trending hashtags: like [hashtag, postId] → $ownerId (rankedCountable) serves top posts within a pinned hashtag, but "top hashtags by total likes" would need groupBy: hashtag — a prefix level.
  • Creator leaderboard: like [postAuthor, postId] → $ownerId serves an author's top posts, but "top authors by likes received" needs the postAuthor level.

Shortening the index is not a workaround: on an indexOnly type, [hashtag] → $ownerId makes (hashtag, liker) the entry itself, so structural uniqueness limits each identity to one like per hashtag ever (and [postAuthor] → $ownerId to one like per author — we reproduced the 40105 on devnet). The prefix-pinned count query (count WHERE hashtag == X on the two-property index) is also refused by the exact-index-match rule, so the totals can't even be read individually.

The data already exists; so does the structure

With countable on the index, the prefix level is a CountTree: every hashtag's (or author's) whole-subtree like total is already maintained on chain — it just has no by-count ordering, so top-K over it would be O(n). GroveDB already has the right primitive: CountIndexedTree (cidx), the same order-statistic tree that backs today's group-level ranking, benchmarked in-repo (grovedb/benches/cidx_benchmark.rs: "Top-k by count: O(log n + k) for cidx vs O(n) for plain CountTree").

So the request is not a new data structure — it is wiring the existing cidx one level higher.

Proposed design

  1. Grammar: allow the ranked axis to name the level it aggregates at, e.g.
    "rankedCountable": { "at": "hashtag" } on [hashtag, postId] (default: today's behavior). Same shape could later generalize to rankedSummable.
  2. Storage/maintenance: a cidx at the declared prefix level, keyed by each group's subtree count. The count deltas already propagate through the CountTree hierarchy on every insert/delete; the new work is re-keying the affected group's cidx entry per delta — the same O(log n) pattern (bench: (k+1)·O(log n) write amplification) as existing ranked maintenance. Composes with preallocated (drained groups stay rankable at 0).
  3. Query/proof: documents.ranked({ groupBy: '<prefix property>' }) dispatches when the group property matches a declared at level; proofs ride the existing indexed-axis PathQuery surface (refactor(drive)!: route ranked and having-range proofs through grovedb's unified PathQuery surface #4488).
  4. Backfill / retrofit: because the CountTree at that level already stores every group's total, the cidx can plausibly be constructed from existing state at activation — allowing the flag to be enabled on an already-registered contract rather than forcing a re-registration. Confirming whether this is feasible would be valuable independent of the rest.

Smaller independent sibling

Relaxing the exact-index-match rule to allow prefix-pinned count queries (count WHERE hashtag == X on [hashtag, postId]) would expose the already-maintained totals as point lookups (e.g. a "likes received" profile stat) even before ranking ships.

Context

Verified against a live 4.2.0-dev.5 devnet with an indexOnly likes contract modeled on the pinned fixture: group-level ranking, structural uniqueness, and the prefix-count refusal all behave as described (exact error/behavior notes available on request). This is the last structural gap for like-derived discovery surfaces on the indexOnly shape — everything else (counts, membership with absence proofs, group-level top-K, delete-by-values) works end to end.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions