feat(ingester): rfc0014 green pt1 — ParquetRecordSink + flush policy - #243
Conversation
Build the buffering production write path (RFC 0014): `ParquetRecordSink` implements `RecordSink`, accumulating mined records per partition and flushing each to a Parquet object on the RFC 0013 `Store` seam (`encode_records_to_parquet` + `put_blocking`, UUIDv7-named). Hybrid flush policy per the specified decisions: - Size — the `emit` that crosses `target_bytes` flushes the partition (RFC0014.1). - Age — `flush_aged` (batch-window tick) flushes partitions whose oldest record reached `max_buffer_age`, inclusive (RFC0014.2). - Rotation — `flush_all` force-flushes EVERY partition, incl. sub-threshold (RFC0014.3). - Hard ceiling — `emit` flushes the largest partition inline before it would exceed `ceiling_bytes`, so buffered bytes never exceed it (RFC0014.4). Buffers are keyed by `PartitionKey` (carries tenant_id) → tenant-scoped by construction (RFC0014.6). A flush failure retains the buffer (the WAL is the durability of record), counted for observability. Un-ignores RFC0014.1/.2/.3/.4/.6 (driven against a LocalFileSystem `Store`, read back to prove no loss + tenant isolation). RFC0014.5 (crash recovery) stays `#[ignore]`d — green part 2 wires the sink into the ingest pipeline + extends the RFC 0008 crash harness; RFC stays `red` until then. The sink takes a `Store` (local or S3) so it's S3-ready; the server-wiring that constructs/injects it + greens RFC0013.6 is the follow-on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 35 minutes and 42 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the 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 credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. 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, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughIntroduces ChangesParquetRecordSink: buffered flush to Parquet with RFC0014 acceptance tests
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Pull request overview
Adds the first “green” slice of RFC 0014’s ingest write path by introducing a production RecordSink implementation that buffers mined records by partition and flushes them to Parquet objects via the RFC 0013 Store seam, along with acceptance tests for the flush policy scenarios.
Changes:
- Introduces
ParquetRecordSink+FlushConfigimplementing hybrid size/age/rotation flush triggers and parquet object writes. - Converts RFC0014 acceptance tests
.1–.4/.6from ignored stubs into real round-trip tests against a local filesystemStore. - Enables UUIDv7 support for deterministic RFC0005-style object naming.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| crates/ourios-ingester/tests/rfc0014_ingest_write_path.rs | Implements RFC0014 scenario tests exercising sink flush behavior and tenant isolation via read-back. |
| crates/ourios-ingester/src/record_sink.rs | Adds ParquetRecordSink buffering + flush policy and Parquet object publication via Store::put_blocking. |
| crates/ourios-ingester/src/lib.rs | Exposes the new record_sink module. |
| crates/ourios-ingester/Cargo.toml | Enables uuid feature v7 for UUIDv7 object naming. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
crates/ourios-ingester/src/record_sink.rs (1)
82-91: ⚡ Quick winExpose
flush_errorsandderive_errorsfor external observability.These error counters are tracked internally but have no public getters, preventing operators from instrumenting them for alerting. Following the crate's
CompactionMetricspattern, consider adding:#[must_use] pub fn flush_errors(&self) -> u64 { self.flush_errors } #[must_use] pub fn derive_errors(&self) -> u64 { self.derive_errors }This enables the metrics layer to export these counters to Prometheus when the sink is wired into the pipeline. As per coding guidelines: "Use Prometheus metrics for every subsystem."
🤖 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-ingester/src/record_sink.rs` around lines 82 - 91, Add public getter methods to the ParquetRecordSink struct to expose the internal error counters for external observability. Implement two public methods, `flush_errors()` and `derive_errors()`, each annotated with the `#[must_use]` attribute, that return the corresponding u64 values from the struct fields flush_errors and derive_errors respectively. This follows the same pattern used in CompactionMetrics and allows the metrics layer to access these counters for Prometheus export without direct field access.Source: Coding guidelines
🤖 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/tests/rfc0014_ingest_write_path.rs`:
- Around line 249-270: The test currently only validates per-file single-tenant
constraints and specific row counts for tenant-x and tenant-y, but does not
prevent extra rows from unexpected tenants from being present. After the
existing assertion on the (x, y) tuple, add two additional assertions: first,
verify that the total count of all rows across all files equals exactly x plus y
(ensuring no extra rows), and second, collect and assert that the complete set
of unique tenants across all files equals exactly the expected set of tenant-x
and tenant-y with no additional tenants present.
---
Nitpick comments:
In `@crates/ourios-ingester/src/record_sink.rs`:
- Around line 82-91: Add public getter methods to the ParquetRecordSink struct
to expose the internal error counters for external observability. Implement two
public methods, `flush_errors()` and `derive_errors()`, each annotated with the
`#[must_use]` attribute, that return the corresponding u64 values from the
struct fields flush_errors and derive_errors respectively. This follows the same
pattern used in CompactionMetrics and allows the metrics layer to access these
counters for Prometheus export without direct field access.
🪄 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: b0c92b3b-2343-44fb-b30d-a2b8696b3911
📒 Files selected for processing (4)
crates/ourios-ingester/Cargo.tomlcrates/ourios-ingester/src/lib.rscrates/ourios-ingester/src/record_sink.rscrates/ourios-ingester/tests/rfc0014_ingest_write_path.rs
…tighten RFC0014 tests (review)
…imate docs (copilot)
The `cargo test --all-features` job started failing on main with `No space left on device (os error 28)` mid-link (compounded by the recurrent rust-lld bus-error). The DataFusion-heavy workspace plus a growing set of integration-test binaries, linked with debug info, outgrew ubuntu-latest's ~14 GiB root disk; #243 added one more test binary and tipped it over. Two post-merge re-runs hit the same wall, so this is capacity, not a flake. Reclaim the large unused preinstalled SDKs (Android ~9 GiB, dotnet, GHC, CodeQL, boost) via a shared script before building, on the two heavy whole-workspace `--all-features` jobs (`test`, `coverage`). No third-party action is added, so the SHA-pinned-actions / Scorecard posture is unchanged. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
RFC 0014
green(part 1) — the buffering production data write path.ParquetRecordSinkreplaces whatNoOpRecordSinkcouldn't do: carry mined records to Parquet objects on the store.What
ParquetRecordSinkimplementsRecordSink, buffering mined records per partition (HashMap<PartitionKey, …>) and flushing each to a Parquet object via the RFC 0013 seam (encode_records_to_parquet+Store::put_blocking, UUIDv7-named per RFC 0005 §3.4). Hybrid flush policy, per thespecifieddecisions:emitcrossingtarget_bytesflushes the partition.flush_aged(batch-window tick) flushes partitions whose oldest record reachedmax_buffer_age(inclusive).flush_allforce-flushes every partition, including sub-threshold ones.emitflushes the largest partition inline before it would exceedceiling_bytes; buffered bytes never exceed it.PartitionKey(carriestenant_id), so a flush only ever writes one tenant's rows.A flush failure retains the buffer (counted) rather than dropping — the WAL is the durability of record (
CLAUDE.md§3.4). The sink takes aStore(local or S3), so it's S3-ready.Tests
RFC0014.1/.2/.3/.4/.6 un-
#[ignore]d — driven against aLocalFileSystemStore, reading the flushed objects back to prove no-loss + per-file single-tenant isolation. 5 pass, 1 ignored. Full workspace green; clippy-D warnings+ fmt clean.Deferred — green part 2
RFC0014.5 (crash: no acknowledged-data loss) stays
#[ignore]d; part 2 wires the sink into the ingest pipeline (miner emit + WAL rotation hook, replacingNoOpRecordSink) and extends the RFC 0008 crash harness. RFC 0014 staysreduntil .5 is green. Then the follow-on server-Store-wiring slice greens RFC0013.6.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Tests