Skip to content

crepe-plan-probe: ARM-B-PREPARE, Arm B's read policy above 1% of centers - #1639

Merged
AdaWorldAPI merged 1 commit into
mainfrom
ccr-d3ce1263-ied9yh
Oct 20, 2026
Merged

AdaWorldAPI merged 1 commit into
mainfrom
ccr-d3ce1263-ied9yh

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Closes ledger (d) item ARM-B-PREPARE for uniform demand (#1636). Lab probe only; no production path changes.

Problem

Arm B reads the located lane ranges of every demanded key exactly, at any selectivity. Above about 1% of centers, its many short ranges cost Lance more per row than one sequential read (plan §2e): at 50% of centers, Arm B was slower than a plain full scan.

What changes

  • addressed::plan_read is a pure function of the located ranges and the table size; it reads no row. Per side it picks one of:
    • Read::Ranges: the exact ranges. Ranges that touch are merged and still count as exact.
    • Read::Coalesced: ranges at most coalesce_gap rows apart are merged.
    • Read::Full: once the merged ranges cover full_permille of the table, the whole table is read without a row filter.
  • Rows read beyond the demand are dropped after the read, by the row's own leading value. No position is taken from another lane's values.
  • ReadPolicy has three constants:
    • EXACT keeps the old behaviour and stays the default of evaluate_addressed.
    • FULL always reads the whole table.
    • DEFAULT coalesces gaps of up to 16 rows and switches to a full read at 100 permille.
    • evaluate_addressed_with takes a policy explicitly.
  • Lookup now reports the read chosen and the number of ranges handed to Lance.
  • examples/addressed_read.rs times every strategy by selectivity, after checking each one against the answer count. Environment variables: READ_N (table size), READ_KS (which k), READ_REPS (runs).

Measurement (release, uniform demand, ms)

rows k exact gap 16 full
50,000 1% 2.72 3.20 4.09
50,000 10% 8.87 7.27 7.46
50,000 50% 35.10 17.97 19.06
200,000 1% 6.94 7.62 18.08
200,000 5% 19.82 18.11 22.15
200,000 50% 128.48 74.29 79.37

The crossover moves with table size:

rows exact ranges win up to tie with the full read
50,000 about 3% 4–6%
200,000 about 5% 7–10%

Between those points the choice is within run-to-run noise: one policy measured 3.9 and 6.1 ms at the same k in one run. DEFAULT switches at 10%, which is never measurably worse on either size.

A full read has a floor of 3 ms at 50,000 rows and 6.7 ms at 200,000 rows, so it loses below the switch. Coalescing is neutral at gap 16 and loses at gap 64 (11.7 vs 6.9 ms at 1% of 200,000 rows). Full tables are in plan §2f.

Tests

  • 5 unit tests on plan_read:

    • the gap boundary: exactly the limit merges, one more row does not;
    • touching ranges stay exact;
    • the full read fires at the threshold row and not one row below;
    • rows added by coalescing count toward the threshold;
    • EXACT never reads in full; FULL always does.
  • Against Lance:

    • B13: every policy answers as EXACT, for every result kind, on a hit, a sponsor branch and a miss.
    • B14: the switch fires on dense demand and stays silent on sparse, checked per side. A full read scans more rows.
    • B15: coalescing hands Lance fewer ranges, with the memory path's answer.
  • 46 tests pass. clippy (-D warnings) and fmt are clean.

  • Disable runs, each red:

    disabled red tests
    the post-read drop B13, B14
    a strict gap comparison 3 unit tests
    the full read 3 unit tests, B14
    coalescing 3 unit tests, B15
    touching ranges counted as coalesced 1 unit test

    B15 does not pin the drop: undemanded rows find no join partner there.

Open

  • The crossover depends on table size, and the switch does not model that.
  • Clustered demand, where coalescing should pay, is not measured.
  • No caller uses DEFAULT by default.

Board: plan §2f and follow-ups, entry 2026-10-20-arm-b-prepare, entries index. The supersession index is unchanged.


Generated by Claude Code

Arm B read the located lane ranges exactly at every selectivity. Above
about 1% of centers its many short ranges cost Lance more per row than
one sequential read (plan §2e).

Each side now chooses its read with `addressed::plan_read`, a pure
function of the located ranges and the table size:
- exact ranges (touching ranges merge and stay exact);
- coalesced ranges across gaps of at most `coalesce_gap` rows;
- a full read once the ranges cover `full_permille` of the table.
Rows read beyond the demand are dropped by the row's own leading value,
so no position is taken from another lane's values.

`ReadPolicy::EXACT` keeps the old behaviour and stays the default of
`evaluate_addressed`; `evaluate_addressed_with` takes a policy.
`ReadPolicy::DEFAULT` (gap 16, full at 100 permille) is pinned from
examples/addressed_read.rs:
- 50,000 rows: exact wins to about 3%, ties at 4-6%, loses from 10%
  (35.1 vs 18-19 ms at 50%).
- 200,000 rows: exact wins to about 5%, ties at 7-10% (128.5 vs
  74-79 ms at 50%).
- Coalescing is neutral at gap 16 and loses at gap 64 on uniform demand.

Tests: 5 unit tests on `plan_read`, B13-B15 against Lance (every policy
answers as EXACT, the switch fires and stays silent per side, coalescing
hands Lance fewer ranges). 46 pass. Disable runs, each red: no post-read
drop, strict gap, never full, never coalesce, touching ranges counted as
coalesced.

Plan §2f, board entry 2026-10-20-arm-b-prepare, entries index.

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

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7af412d6-73ba-4f21-bef3-ef7a57a562ca
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.

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.

@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review October 20, 2026 23:23
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T23:25:38.129739Z b036821 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@AdaWorldAPI
AdaWorldAPI merged commit 1798cb7 into main Oct 20, 2026
14 checks passed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b03682176f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let wanted: Vec<u32> = u32::try_from(restricted(i).expect("restricted"))
.into_iter()
.collect();
let (how, read_ranges, got, rows_io) = read_side(&ds, a, &r, &wanted, policy).await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report the actual mask used by the selected read

When ReadPolicy::FULL or a coalescing policy is selected here, the unchanged SideIo construction still sets mask_rows to n, the number of rows in the original exact ranges. This violates SideIo::mask_rows's documented |R| meaning: a full read has no mask and coalescing masks additional gap rows. Consumers therefore see a nonexistent or undersized mask and cannot accurately compare it with rows_scanned/rows_returned; set it to None for Read::Full and to the merged-range span for masked reads in both side loops.

Useful? React with 👍 / 👎.

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