Skip to content

feat(bench): RFC 0031 — three-point selectivity curve in the indicative run - #480

Merged
jensholdgaard merged 4 commits into
mainfrom
rfc0031-selectivity-curve
Jul 11, 2026
Merged

feat(bench): RFC 0031 — three-point selectivity curve in the indicative run#480
jensholdgaard merged 4 commits into
mainfrom
rfc0031-selectivity-curve

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented Jul 11, 2026

Copy link
Copy Markdown
Owner

What

Extends the RFC 0031 indicative comparative run from a single measured pair to a three-point selectivity curve, all against one Loki container and one full-corpus replay:

pair family expected rows reported margin
severity threshold (picked, run #5/#6's pair) L2 ~1 on v8 m_l2 (10)
time-window slice, k=100 L6 100 f_l6 (3)
time-window slice, k=2000 L6 2000 f_l6 (3)

Why

Run #6's refined storage-side number — 5.95× on a 1-row answer (vs 182.4× on decompressed totalBytesProcessed) — is a single point at the extreme-selectivity end, where Ourios's fixed per-query footer/metadata reads dominate the bytes figure. The testable prediction: that fixed cost amortizes as the result set grows while Loki's chunk scan grows with it. This PR measures the curve instead of arguing about the point. Gates stay REPORTED, not asserted (§7 margins provisional); equivalence stays asserted per pair.

How the window pairs stay honest

  • Clean edges: a window [a, b) is only picked if no earlier record shares a and the next record past the window sits ≥ 2 ns beyond b = last + 1 — so both systems select identical rows regardless of range-end inclusivity semantics.
  • Poison rejection: any window containing a zero-time_unix_nano row's observed-fallback timestamp is rejected. Both systems would return such a row (Ourios windows the RFC 0005 effective timestamp; Loki stamps observed time), but with different answer timestamps — a guaranteed equivalence mismatch, so the picker refuses rather than letting the run die late.
  • Mid-corpus preference: candidate windows rank by distance from the corpus centre, deterministic tiebreak.

Locally proven before spending a dispatch run

  • pick_window_pair unit tests: centring, dirty-edge avoidance, poison avoidance, insufficient-input None.
  • collect_service_timestamps fixture pass (clean/poison split, unknown service empty).
  • window_pair_dsl_slices_the_fixture: builds the fixture store and proves the bare-service DSL parses and the now = end / window = end − start mapping slices exactly [start, end) against the querier's ts ≥ now − window ∧ ts < now filter.

Invariants / hazards

Bench-harness + workflow-summary change only; no hot path, no schema, no miner change. The Loki config deviations are unchanged from #478 (all ingest-side, all in Loki's favour, documented at the flag site). The job-summary grep widens (-A8-A60) for the multi-section report.

Checks run

cargo fmt --all --check, cargo clippy --all-targets --all-features -- -D warnings, cargo nextest run -p ourios-bench (120 passed, container tests #[ignore]d as designed). The dispatch run itself is the CI-only validation, like the prior six.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WQY9wfrfRggqSpMLH8Xj3Y

Summary by CodeRabbit

  • New Features

    • Expanded comparative benchmark reporting with additional calibration scenarios and time-window measurements.
    • Reports now include storage-side and processed-byte comparisons for each measurement.
  • Bug Fixes

    • Improved selection of clean, centered measurement windows while avoiding invalid timestamp data.
    • Comparative checks now verify equivalent results across measurement methods.
  • Tests

    • Added coverage for time-window selection, timestamp handling, edge cases, and insufficient data.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 46 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4366e71e-8526-4f45-9833-af1250db3478

📥 Commits

Reviewing files that changed from the base of the PR and between b2816e2 and 8c884b5.

📒 Files selected for processing (1)
  • crates/ourios-bench/tests/rfc0031_comparative.rs
📝 Walkthrough

Walkthrough

The RFC0031 comparative harness now selects deterministic clean time windows, measures multiple Ourios and Loki query pairs, validates per-pair equivalence, and reports additional Loki byte metrics. The workflow captures a larger report section in the GitHub job summary.

Changes

RFC0031 calibration flow

Layer / File(s) Summary
Timestamp partitioning and window selection
crates/ourios-bench/tests/rfc0031_comparative.rs
Corpus JSONL discovery is centralized; clean and poison timestamps are partitioned, and exact-size centered windows are selected with edge and poison constraints covered by unit tests.
Multi-pair measurement and reporting
crates/ourios-bench/tests/rfc0031_comparative.rs, .github/workflows/comparative-bench.yml
The indicative run measures severity and time-window pairs against Ourios and Loki, checks per-pair multiset equivalence, reports storage-side and processed bytes, and captures a larger job-summary report block.

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

Sequence Diagram(s)

sequenceDiagram
  participant ComparativeRun
  participant Ourios
  participant Loki
  participant JobSummary
  ComparativeRun->>ComparativeRun: build_pair_specs
  ComparativeRun->>Ourios: query each PairSpec
  ComparativeRun->>Loki: query_range each PairSpec
  Loki-->>ComparativeRun: rows and byte metrics
  Ourios-->>ComparativeRun: expected rows
  ComparativeRun->>ComparativeRun: compare per-pair multisets
  ComparativeRun->>JobSummary: write expanded report
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding a three-point selectivity curve to the RFC 0031 indicative run.
Description check ✅ Passed The description is mostly complete and covers the summary, rationale, behavior, and checks run, though it omits the template's Related section.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rfc0031-selectivity-curve

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.

…ve run

Run #6's storage-side result (5.95x on a 1-row answer) is a single point
at the extreme-selectivity end, where Ourios's fixed per-query footer/
metadata reads dominate. This extends the indicative run to a curve:
the severity pair (L2 family) plus two time-window slices (~100 and
~2000 rows on the picked service, L6 family, reported under f_l6), all
measured against ONE container and ONE corpus replay.

Window pairs are chosen with clean edges (no shared timestamp at the
start, >=2 ns gap past the end) so [a, b) selects identical rows on both
systems regardless of range-end inclusivity, and windows containing any
zero-time_unix_nano row's observed-fallback timestamp are rejected —
both systems would return such a row with DIFFERENT answer timestamps,
a guaranteed equivalence mismatch.

Locally proven before spending a run: picker edge/poison/centring unit
tests, a fixture pass for the timestamp collector, and a fixture-store
test that the bare-service DSL + now/window mapping slices exactly
[start, end). Equivalence stays asserted per pair; gates stay REPORTED
under the provisional §7 margins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQY9wfrfRggqSpMLH8Xj3Y

Copilot AI 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.

Pull request overview

Extends the RFC 0031 indicative comparative run (in ourios-bench) from a single extreme-selectivity measurement to a three-point selectivity curve (1 row + ~100 rows + ~2000 rows) while keeping equivalence asserted per pair and bytes gates reported under provisional margins. This improves the benchmark’s diagnostic value by measuring how fixed per-query costs amortize as result sizes grow.

Changes:

  • Add corpus rescanning helpers to collect per-service timestamps and deterministically pick “clean-edge” time windows (including poison timestamp avoidance) for k-row slices.
  • Refactor the indicative run into a set of PairSpecs and run/verify/report each pair against one Loki container + one full-corpus replay.
  • Expand the workflow job-summary grep window to include the multi-section report.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
crates/ourios-bench/tests/rfc0031_comparative.rs Refactors the comparative run to measure three pairs and adds helper logic + unit tests for deterministic clean window selection and poison avoidance.
.github/workflows/comparative-bench.yml Widens the summary extraction (grep -A60) to capture the expanded multi-pair report block.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-bench/tests/rfc0031_comparative.rs Outdated
The [start, end) mapping only holds for the time-window slices; the
severity pair windows the full corpus and lets the predicate select.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQY9wfrfRggqSpMLH8Xj3Y

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread crates/ourios-bench/tests/rfc0031_comparative.rs
Comment thread crates/ourios-bench/tests/rfc0031_comparative.rs
The +1 was provably safe (valid() already gated it), but the checked
form keeps that safety local if the two sites ever decouple.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQY9wfrfRggqSpMLH8Xj3Y

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comment thread crates/ourios-bench/tests/rfc0031_comparative.rs
Comment thread crates/ourios-bench/tests/rfc0031_comparative.rs Outdated
Comment thread crates/ourios-bench/tests/rfc0031_comparative.rs
Comment thread crates/ourios-bench/tests/rfc0031_comparative.rs

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
crates/ourios-bench/tests/rfc0031_comparative.rs (2)

938-991: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a unit test for build_pair_specs itself.

The function is pure and fully testable without a corpus/network dependency, but only pick_window_pair/collect_service_timestamps (its dependencies) have dedicated tests. The only exerciser of the spec-assembly logic itself (margin assignment, now/window mapping, label text) is the #[ignore]d rfc0031_indicative_comparative_run, which needs OURIOS_COMPARATIVE_CORPUS and a live Loki container — it won't run in normal CI. A regression here (e.g. swapped m_l2/f_l6, or a broken now/window formula) would go undetected until the dispatch workflow runs.

Consider a test that builds a small SelectivePair plus synthetic clean_ts/poison_ts, calls build_pair_specs, and asserts on margin, start/end, and the now/window invariant per spec.

As per coding guidelines: "Unit tests must be next to the code and are mandatory for anything non-trivial".

🤖 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 `@crates/ourios-bench/tests/rfc0031_comparative.rs` around lines 938 - 991, Add
a focused unit test alongside build_pair_specs that constructs a small
SelectivePair and synthetic clean_ts/poison_ts inputs, then verifies the
generated specs’ count, margins, labels, start/end values, expected_rows, and
now/window mappings, including the now == end and window == end - start
invariant for window specs. Keep the test independent of corpus, network, or
ignored comparative-run infrastructure.

Source: Coding guidelines


649-665: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Service-name matching is inconsistent with pick_selective_pair's extraction.

pick_selective_pair buckets resources with no service.name attribute under the empty-string service key (.unwrap_or_default()). Here, matches_service short-circuits to false whenever the attribute chain resolves to None, so it can never match service == "". If the severity-pair picker ever selects the empty-string bucket, this function silently returns empty clean/poison lists, and the later pick_window_pair call panics in build_pair_specs instead of surfacing the real cause.

Extracting a shared helper (used by both pick_selective_pair and collect_service_timestamps) that returns the resolved service string (defaulting to "") and then compares by equality would close this gap and remove the duplicated attribute-lookup logic.

♻️ Proposed fix sketch
+fn resource_service_name(
+    rl: &opentelemetry_proto::tonic::logs::v1::ResourceLogs,
+) -> String {
+    rl.resource
+        .as_ref()
+        .and_then(|r| r.attributes.iter().find(|kv| kv.key == "service.name"))
+        .and_then(|kv| kv.value.as_ref())
+        .and_then(|v| v.value.as_ref())
+        .and_then(|v| match v {
+            opentelemetry_proto::tonic::common::v1::any_value::Value::StringValue(s) => {
+                Some(s.clone())
+            }
+            _ => None,
+        })
+        .unwrap_or_default()
+}
...
-                let matches_service = rl
-                    .resource
-                    .as_ref()
-                    .and_then(|r| r.attributes.iter().find(|kv| kv.key == "service.name"))
-                    .and_then(|kv| kv.value.as_ref())
-                    .and_then(|v| v.value.as_ref())
-                    .is_some_and(|v| {
-                        matches!(
-                            v,
-                            opentelemetry_proto::tonic::common::v1::any_value::Value::StringValue(s)
-                                if s == service
-                        )
-                    });
-                if !matches_service {
+                if resource_service_name(rl) != service {
                     continue;
                 }
🤖 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 `@crates/ourios-bench/tests/rfc0031_comparative.rs` around lines 649 - 665,
Unify service-name extraction for pick_selective_pair and
collect_service_timestamps by introducing a shared helper that returns the
service attribute value, defaulting to an empty string when absent or
non-string. Replace the matches_service attribute chain with equality against
that resolved string so the empty-string bucket is matched consistently.
🤖 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 `@crates/ourios-bench/tests/rfc0031_comparative.rs`:
- Around line 938-991: Add a focused unit test alongside build_pair_specs that
constructs a small SelectivePair and synthetic clean_ts/poison_ts inputs, then
verifies the generated specs’ count, margins, labels, start/end values,
expected_rows, and now/window mappings, including the now == end and window ==
end - start invariant for window specs. Keep the test independent of corpus,
network, or ignored comparative-run infrastructure.
- Around line 649-665: Unify service-name extraction for pick_selective_pair and
collect_service_timestamps by introducing a shared helper that returns the
service attribute value, defaulting to an empty string when absent or
non-string. Replace the matches_service attribute chain with equality against
that resolved string so the empty-string bucket is matched consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f7205c6f-46ae-47a3-b871-8f25e92e6f24

📥 Commits

Reviewing files that changed from the base of the PR and between b05fc86 and b2816e2.

📒 Files selected for processing (2)
  • .github/workflows/comparative-bench.yml
  • crates/ourios-bench/tests/rfc0031_comparative.rs

…hmetic

Copilot caught a real latent hazard: the severity pair queries the full
corpus window, but the picker only EXCLUDED zero-time_unix_nano rows
from its bands — it never checked whether the picked predicate could
select one. A zero-time row with severity >= threshold (DSL side) or
carrying the pair's text at any severity (LogQL side) is returned by
both systems with different answer timestamps: a deterministic
equivalence failure. Runs #5/#6 passed by corpus luck, not by
construction.

Zero-time rows are now tallied into per-service POISON bands and any
candidate whose predicate could select one is disqualified, on both
directions of the predicate. Unit-tested directly on hand-built bands.

Also: checked_add on the two flagged window-end computations so a
corrupted corpus fails loudly instead of wrapping in release mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQY9wfrfRggqSpMLH8Xj3Y

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

crates/ourios-bench/tests/rfc0031_comparative.rs:763

  • build_pair_specs assumes the service picked for the severity pair can also supply clean window slices of k=100 and k=2000. However select_pair_candidates/pick_selective_pair currently select purely on the rare-severity band (1..=4000 rows) and do not ensure the chosen service has >=2000 clean records; in that case the dispatch-only run will panic!("no clean 2000-row window …") even though a different service/threshold could work. Consider filtering candidates (or services) up-front to require at least the largest slice size worth of clean rows, or repicking when pick_window_pair returns None.
    for (svc, (bands, poison)) in per_service {
        if !safe(svc) {
            continue;
        }
        for &threshold in bands.keys() {

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