cap: hoist the grant scan and the root row out of the per-row loop #93

Merged
PerishFire merged 1 commit from cap-hoist into main 2026-07-21 04:54:48 +00:00
Owner
No description provided.
cap: hoist the grant scan and the root row out of the per-row loop
All checks were successful
guard / guard (pull_request) Successful in 29s
f3a85d411a
Measured first, on a counting Wire. Authority was costing a database
round trip per row, because sift asks held() per row and check() rescanned
the whole @grant table each time. For the canonical capability shape --
a root-chained unit under a pred grant -- it was three round trips per
row: the grant scan, the anchors walk up the root chain, and descend
re-reading the very row anchors had just fetched. Every one of the three
returned identical data for every row of the operation.

  flat, 400 rows, real postgres:  401 reads -> 2      55.4ms -> 3.4ms
  flat, 1600 rows, sqlite mem:   1601 reads -> 2      57.5ms -> 10.2ms
  rooted, 400 rows, sqlite mem:  1201 reads -> 402    35.5ms -> 14.5ms

Two cuts. Core gains a Deeds cache holding the live @grant rows stamped
with the write generation the Stash already tracks, so a grant put or end
invalidates it through the existing bump; check and broad now take the
rows instead of fetching them. Stash::bump had to stop honouring the
[cache] switch -- a generation is a fact about writes, not a cache policy,
and read/keep already gate on `on`. Then anchors carries the Row it
already read down the chain as a Hop, so descend reads nothing at all and
stops being async.

check/broad/held collapse their who+verb+unit+mark arguments into a Plea.
That is what makes room for the deeds argument, and it pays down param
debt at the same time: 91 -> 88 debts, param 9 -> 6. broad says "no
particular row" explicitly via Mark::none() rather than taking a
different argument shape from check.

tests/query/probe: a counting Wire pins the round-trip counts (flat = 2
reads and 1 grant scan for 40 rows; rooted = 42 and 1), so the win cannot
be silently lost. fresh proves the cache still sees a grant put and a
revoke -- neutering the generation check fails it, and only it.

Still per-row and named for the next cut: anchors re-reads the same root
row for every row that shares a parent. Predicate pushdown remains a
separate arc, not attempted here.

guard clean, 43 tests green on sqlite and on real postgres.
PerishFire deleted branch cap-hoist 2026-07-21 04:54:49 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
PerishLab/keel!93
No description provided.