Skip to content

feat(ingester): scaffold crate + background compaction runner (RFC 0009 §3.2) - #101

Merged
jensholdgaard merged 4 commits into
mainfrom
feat/ingester-scaffold
Jun 3, 2026
Merged

feat(ingester): scaffold crate + background compaction runner (RFC 0009 §3.2)#101
jensholdgaard merged 4 commits into
mainfrom
feat/ingester-scaffold

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented Jun 3, 2026

Copy link
Copy Markdown
Owner

What

Scaffolds the ourios-ingester crate (the ingester role, CLAUDE.md §1/§7 — now a workspace member) and lands its first working subsystem: the background compaction daemon (RFC 0009 §3.2). Closes the "Background task in the ingester" slice on epic #94.

New crate — scope & RFC standing

A new crate is an architectural commitment (§7). ourios-ingester is in the §7 target layout, and RFC 0009 §3.2 (specified) says compaction is "hosted in the ingester role" — so this scaffold is RFC-backed. It deliberately implements only the compaction host; the OTLP ingest path (RFC 0003, still drafted) and WAL-before-ack (RFC 0008) are a documented placeholder (receiver module) that lands when those RFCs reach red. No ingest is front-run.

Changes

  • run_sweep(bucket_root, now_unix_nanos, policy) -> Result<SweepReport, IngestError> — one synchronous, deterministic pass: enumerate tenants (decode tenant_id=<enc> dirs), plan_candidates per tenant, compact_partition each, accumulate a SweepReport (tenants scanned, partitions compacted, rows, gc_failures). The testable unit.
  • Compactor::run(on_sweep) — the daemon loop: a tokio::time::interval ticks run_sweep on the blocking pool (compaction is blocking FS/Parquet work) and hands each Result to a caller-supplied observer, so a failing sweep is observed, not fatal, and the loop keeps ticking. (OTel metering is a later slice; the observer is the seam.)
  • ourios_parquet::percent_decode_tenant — inverse of the tenant encoder, needed to recover raw tenant ids from directory names when sweeping. Round-trip + malformed-escape tests.

Tests

  • run_sweep: compacts a sealed candidate; skips an unsealed partition; scans every tenant (compacts only the candidate one); empty store → zero.
  • Compactor::run: spawns the loop under tokio, awaits the first sweep result via the observer, asserts the candidate was compacted, then cancels.
  • percent_decode_tenant: round-trips the encoder across unreserved/delimiter/UTF-8 tenants; rejects malformed escapes.

Invariants / hazards

  • §3.7 tenant isolation: the sweep operates per decoded tenant; compact_partition (via Reader::open_partition) validates row-vs-path, and plan_candidates only returns canonical partition dirs (feat(parquet): add compaction candidate planner (RFC 0009 §3.3) #100).
  • §3.4 WAL-before-ack untouched: compaction is a background, off-hot-path task; it reads/rewrites already-published Parquet only.

Verification (local)

  • cargo fmt --all --check
  • cargo clippy -p ourios-ingester -p ourios-parquet --all-targets --all-features -- -D warnings
  • cargo test -p ourios-ingester -p ourios-parquet --all-features ✅ (ingester 5; parquet 52 + integration)

Not in scope (later epic #94 slices)

Standalone orphan-GC sweep, audit events + telemetry (incl. the H4 file-size histogram, and OTel metering of SweepReport), and the crash-recovery/proptest + D2/D3 bench. Wiring the compactor into a running server binary awaits ourios-server.

Epic: #94.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • New ingester crate with a background compaction service that runs on a schedule and reports sweep summaries
    • Tenant identifier percent-decode support and improved round‑trip encoding/decoding
    • Scaffolded receiver module prepared for future ingestion/OTLP functionality
  • Chores

    • Added the ingester crate to the workspace member list
  • Tests

    • Extended tests for tenant encoding/decoding and compaction behaviors

Review Change Stack

…09 §3.2)

Establishes the `ourios-ingester` crate (CLAUDE.md §7 layout; added to
the workspace) as the ingester role's home, and lands its first
working subsystem: the background compaction daemon.

- `run_sweep(bucket_root, now_unix_nanos, policy)` — one synchronous,
  deterministic pass: enumerate tenants (decoding `tenant_id=<enc>`
  dirs), `plan_candidates` per tenant, `compact_partition` each, into a
  `SweepReport`. This is the unit the tests exercise.
- `Compactor::run(on_sweep)` — the daemon loop: a tokio interval ticks
  `run_sweep` on the blocking pool (compaction is blocking I/O) and
  hands each result to a caller observer, so a failing sweep is
  observed, not fatal (telemetry wiring is a later slice).
- `ourios-parquet::percent_decode_tenant` — inverse of the tenant
  encoder, needed to recover raw tenant ids from directory names when
  sweeping; round-trip + malformed-escape tests.
- `receiver` module is a documented placeholder: the OTLP ingest path
  (RFC 0003, `drafted`) and WAL-before-ack (RFC 0008) land when those
  RFCs reach `red`. This scaffold implements only the RFC 0009 §3.2
  compaction host; it does not front-run the receiver.

Tests: sweep compacts a sealed candidate, skips unsealed, scans every
tenant, empty store is zero; the daemon loop runs a sweep end-to-end
under tokio until cancelled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jensholdgaard
jensholdgaard requested a review from Copilot June 3, 2026 10:08
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3bdd30dc-0dc7-40ce-8517-f7e506d8aa6c

📥 Commits

Reviewing files that changed from the base of the PR and between d634743 and 35b3b69.

📒 Files selected for processing (2)
  • crates/ourios-ingester/src/compactor.rs
  • crates/ourios-parquet/src/partition.rs

📝 Walkthrough

Walkthrough

This PR adds a new ourios-ingester crate (registered in the workspace) implementing a background compaction daemon that scans tenant directories, plans sealed-partition compaction candidates, applies compaction, and reports sweep metrics. It also adds percent_decode_tenant to ourios-parquet and exports it.

Changes

Ingester Crate and Background Compaction

Layer / File(s) Summary
Workspace and Crate Registration
Cargo.toml, crates/ourios-ingester/Cargo.toml
Register ourios-ingester in the workspace members list and add the crate manifest with path deps on ourios-core and ourios-parquet, tokio features, tempfile dev-dep, and workspace lints.
Parquet Tenant Decoding Support
crates/ourios-parquet/src/partition.rs, crates/ourios-parquet/src/lib.rs
Add percent_decode_tenant to decode %HH escapes into bytes with UTF-8 validation; factor is_unreserved and hex_value helpers; add round-trip and malformed-input tests; export it from the parquet partition public API.
Ingester Crate Module Structure
crates/ourios-ingester/src/lib.rs, crates/ourios-ingester/src/receiver.rs
Add crate-level docs describing the ingester, forbid unsafe, declare compactor and receiver modules, re-export compaction API, and include an empty receiver placeholder.
Compactor Types and Sweep Logic
crates/ourios-ingester/src/compactor.rs
Introduce IngestError and SweepReport; implement run_sweep to enumerate tenants, percent-decode tenant IDs, plan candidates, call compact_partition, and aggregate metrics; add now_unix_nanos helper and module imports/docs.
Compactor Daemon, Blocking Execution, and Tests
crates/ourios-ingester/src/compactor.rs
Add Compactor::new and async Compactor::run using a Tokio interval with spawn_blocking, propagate sweep results to a callback, and include test helpers, unit tests covering sweep scenarios, and an async test verifying the daemon runs at least one sweep.

Sequence Diagram(s)

sequenceDiagram
  participant Compactor
  participant run_sweep
  participant Planner as plan_candidates
  participant Merger as compact_partition
  participant Storage as bucket_root

  Compactor->>run_sweep: invoke with bucket_root, now_unix_nanos, policy
  run_sweep->>Storage: list tenants under data/
  run_sweep->>Planner: plan_candidates(tenant)
  Planner-->>run_sweep: candidate list
  run_sweep->>Merger: compact_partition(candidate)
  Merger-->>run_sweep: CompactionOutcome
  run_sweep-->>Compactor: SweepReport
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hop through buckets under moonlit skies,
Decoding tenants where percent-escape lies,
I sweep and compact with a patient beat,
Stitching files together so storage feels neat,
Hooray — the ingester tidies up bytes!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: scaffolding the ingester crate and implementing the background compaction runner per RFC 0009 §3.2.
Description check ✅ Passed The description is comprehensive and well-structured, covering all template sections (Summary, Related, Checklist) with detailed explanations of the changes, tests, and verification performed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/ingester-scaffold

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

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

This PR introduces a new workspace crate, ourios-ingester, and implements the first ingester subsystem: a background compaction runner/daemon that periodically sweeps the object-store layout, plans sealed compaction candidates per tenant, and invokes Parquet compaction (per RFC 0009 §3.2). It also adds tenant percent-decoding support to ourios-parquet to enable enumerating tenants from tenant_id=<enc> directory names.

Changes:

  • Add ourios-ingester crate scaffold with a synchronous run_sweep (deterministic, testable) and an async Compactor::run daemon loop using spawn_blocking.
  • Add percent_decode_tenant to ourios-parquet and re-export it for use by sweep enumeration logic.
  • Register the new crate in the workspace and lockfile.

Reviewed changes

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

Show a summary per file
File Description
crates/ourios-parquet/src/partition.rs Adds percent_decode_tenant plus round-trip/malformed-escape tests.
crates/ourios-parquet/src/lib.rs Re-exports percent_decode_tenant from the crate root.
crates/ourios-ingester/src/receiver.rs Placeholder module documenting deferred ingest/WAL work per RFC maturity.
crates/ourios-ingester/src/lib.rs New crate root: documents role/RFC scope and exports compactor API.
crates/ourios-ingester/src/compactor.rs Implements tenant enumeration, run_sweep, daemon loop, and tests.
crates/ourios-ingester/Cargo.toml Declares the new crate and its dependencies (ourios-core, ourios-parquet, tokio).
Cargo.toml Adds crates/ourios-ingester to the workspace members list.
Cargo.lock Records the new workspace package entry for ourios-ingester.

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

Comment thread crates/ourios-ingester/src/compactor.rs
Comment thread crates/ourios-ingester/src/compactor.rs
@jensholdgaard
jensholdgaard requested a review from Copilot June 3, 2026 10:15
@jensholdgaard

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Actionable comments posted: 3

🤖 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.

Inline comments:
In `@crates/ourios-ingester/src/compactor.rs`:
- Around line 86-95: The loop currently uses `?` on `plan_candidates` and
`compact_partition`, which aborts the entire sweep on the first error; change
both calls to handle errors locally instead of propagating: call
`plan_candidates(bucket_root, &tenant, now_unix_nanos, policy)` and
`compact_partition(bucket_root, &partition)` and match on the Result — on Err
log the error (including tenant/partition identifiers) and increment an
appropriate failure counter on `report` (add `plan_failures` /
`partition_failures` if needed), then continue to the next tenant/partition; on
Ok proceed as before and update `report.partitions_compacted`,
`report.rows_compacted` and `report.gc_failures` from `outcome`. This ensures
`Compactor::run` won’t be aborted by a single tenant/partition error.

In `@crates/ourios-ingester/src/lib.rs`:
- Around line 15-16: The doc comment contains a broken intra-doc link: the code
span "[`ourios_parquet:: plan_candidates`]" is split across lines causing
rustdoc to emit broken_intra_doc_links; edit the module-level doc comment in
lib.rs and join the link into a single uninterrupted path like
[`ourios_parquet::plan_candidates`] (remove the newline between :: and
plan_candidates) so the intra-doc link resolves correctly.

In `@crates/ourios-parquet/src/partition.rs`:
- Around line 198-213: percent_decode_tenant currently accepts any non-'%' byte
verbatim which lets non-canonical names (e.g., spaces, non-ASCII) through;
update percent_decode_tenant to reject any unescaped byte that would have been
percent-encoded by percent_encode_tenant by validating each non-'%' byte against
the canonical safe set (the same character class used in percent_encode_tenant)
and return None if a byte is not in that set or if any percent-sequence is
invalid; keep the existing hex-decoding logic for '%' sequences but add the
per-byte check for non-'%' bytes so only canonical unescaped characters are
allowed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e89d531-f3b4-4dbc-bc7d-f0c097176455

📥 Commits

Reviewing files that changed from the base of the PR and between 2c1b2f9 and 662c78b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Cargo.toml
  • crates/ourios-ingester/Cargo.toml
  • crates/ourios-ingester/src/compactor.rs
  • crates/ourios-ingester/src/lib.rs
  • crates/ourios-ingester/src/receiver.rs
  • crates/ourios-parquet/src/lib.rs
  • crates/ourios-parquet/src/partition.rs

Comment thread crates/ourios-ingester/src/compactor.rs
Comment thread crates/ourios-ingester/src/lib.rs Outdated
Comment thread crates/ourios-parquet/src/partition.rs

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 7 out of 8 changed files in this pull request and generated 2 comments.

Comment thread crates/ourios-ingester/src/compactor.rs Outdated
Comment thread crates/ourios-ingester/src/compactor.rs
@jensholdgaard
jensholdgaard requested a review from Copilot June 3, 2026 11:49
@jensholdgaard

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@crates/ourios-parquet/src/partition.rs`:
- Around line 191-197: The decoder currently accepts lowercase hex via
hex_value, letting `%2f` decode though percent_encode_tenant only emits `%2F`;
update the percent-decoding logic (the code using hex_value in the percent
decode function used by tenants()/percent_decode_tenant) to reject lowercase hex
digits: when parsing the two hex chars after `%`, verify each char matches
'0'..'9' or 'A'..'F' (uppercase) before converting (or otherwise check that the
original characters are uppercase) and return None on any lowercase a..f so the
decoder is the strict inverse of percent_encode_tenant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 052614ad-a267-4255-b39e-dada6d6da6a0

📥 Commits

Reviewing files that changed from the base of the PR and between f0e4632 and d634743.

📒 Files selected for processing (3)
  • crates/ourios-ingester/src/compactor.rs
  • crates/ourios-ingester/src/lib.rs
  • crates/ourios-parquet/src/partition.rs

Comment thread crates/ourios-parquet/src/partition.rs

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 7 out of 8 changed files in this pull request and generated 3 comments.

Comment thread crates/ourios-parquet/src/partition.rs
Comment thread crates/ourios-parquet/src/partition.rs
Comment thread crates/ourios-ingester/src/compactor.rs Outdated
@jensholdgaard
jensholdgaard requested a review from Copilot June 3, 2026 11:57
@jensholdgaard

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 7 out of 8 changed files in this pull request and generated no new comments.

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