feat(parquet): store-backed Writer + Reader ctors for the RFC 0019 compactor - #294
Conversation
The compactor (RFC 0019 slice 2b) needs to read and write Parquet through a configured Store (local or S3), but Writer::open and Reader::open_partition pin Store::local internally. Add the S3-capable seam additively: Writer::open_in (+ open_in_with_zstd_level) takes an already-built Store, with Writer::open now delegating to it after building the local store (the ~40 path call sites are untouched). WrittenFile gains key + bytes_written so a store-backed writer is addressable and sizable without stat-ing a local path (which S3 can't do). Reader::open_partition_bytes mirrors open_partition over store bytes, keeping the RFC 0005 §3.9 row-vs-path validation. Unit tests cover the store round-trip and the mismatched-partition abort. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 33 minutes and 34 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 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 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)
📝 WalkthroughWalkthroughAdds Writer and Reader store-backed bytes API
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 a Store-backed construction seam to the ourios-parquet writer/reader so the RFC 0019 compactor can read/write Parquet through an already-configured Store (local or S3) without changing existing local call sites.
Changes:
- Introduces
Writer::open_in/open_in_with_zstd_levelto write via an existingStore, while keepingWriter::opendelegating to the local-store path. - Extends
WrittenFilewith backend-agnostic output identifiers (key) and size reporting (bytes_written). - Adds
Reader::open_partition_bytesto validate/read partitioned Parquet data from in-memory bytes (store fetch).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| crates/ourios-parquet/src/writer.rs | Adds Store-backed writer constructors and augments WrittenFile with key/size metadata; includes new unit tests for store round-trip. |
| crates/ourios-parquet/src/reader.rs | Adds a partition-aware “read from bytes” constructor to support store-backed compaction reads while preserving §3.9 validation. |
Comments suppressed due to low confidence (1)
crates/ourios-parquet/src/writer.rs:176
Writer::open_with_zstd_levelno longer validateszstd_levelbefore creating the partition directory / opening the store. As a result, an invalid level now has observable filesystem side-effects (empty partition dir) and does more work before returningWriterError::Parquet, which contradicts the prior “fail fast” contract described in the removed comment block.
// Ensure the store root (and the partition dir) exist:
// `Store::local` canonicalises `bucket_root`, which must
// therefore exist; the object-store `put` on close creates any
// remaining parents.
let dir = partition.data_path(bucket_root);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot: Writer::open's doc claimed it creates the parquet file up front, but it's buffer-and-put (nothing published until close); reword to say only the local partition dir is created. And open_partition_bytes recorded a synthetic <object-store> path, so a PartitionMismatch wouldn't name the offending object in compaction logs; take the object key for error context (no prior callers). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot: the refactor deferred zstd validation to the delegate, which runs after create_dir_all, so an invalid level left an empty partition directory behind. Validate up front in open_with_zstd_level (the delegate re-validates, cheaply) so invalid input stays side-effect free. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
crates/ourios-parquet/src/writer.rs (1)
825-904: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the store-backed reconstruction test property-based.
This adds good seam coverage, but it exercises only one fixed record shape. Since this is a Parquet writer reconstruction path, please add a
proptestvariant alongside the deterministic store/key assertions. Also fix theRFC0009.5typo in the mismatch test comment while touching this block. As per coding guidelines,**/crates/ourios-{miner,parquet,querier}/**/*.rs: “Use property tests (proptest) for anything with an invariant: the template miner, the Parquet writer, the query planner. Reconstruction is always a property test.”🤖 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-parquet/src/writer.rs` around lines 825 - 904, The store-backed reconstruction coverage in Writer::open_in and Reader::open_partition_bytes is still only using one fixed record shape, so add a proptest-based variant that exercises the same key/size/reconstruction invariants across generated records while keeping the existing deterministic assertions. Reuse the existing Writer::open_in, WrittenFile, and Reader::open_partition_bytes flow, but parameterize the records so the reconstruction path is property-tested as required for the Parquet writer. While touching the open_partition_bytes_rejects_a_mismatched_partition test, correct the RFC0009.5 typo in the comment.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-parquet/src/reader.rs`:
- Around line 112-137: Clean up the docs for Reader::open_partition_bytes and
Reader::read_all: replace the stray RFC0009.5 reference with the same RFC 0005
§3.9 wording used elsewhere, and update the read_all documentation to explicitly
mention open_partition_bytes as another entry point that triggers the same
partition validation path.
In `@crates/ourios-parquet/src/writer.rs`:
- Around line 243-247: Update the rustdoc for the `final_path()` accessor in
`writer.rs` to reflect store-backed behavior: it should describe the returned
path as the object key rendered as a path for store-backed writes, not an
absolute store-root-joined filesystem path. Keep the documentation aligned with
the `final_path` construction in the writer logic and mention `WrittenFile::key`
as the canonical value for S3/store-backed reads so callers are not misled.
---
Nitpick comments:
In `@crates/ourios-parquet/src/writer.rs`:
- Around line 825-904: The store-backed reconstruction coverage in
Writer::open_in and Reader::open_partition_bytes is still only using one fixed
record shape, so add a proptest-based variant that exercises the same
key/size/reconstruction invariants across generated records while keeping the
existing deterministic assertions. Reuse the existing Writer::open_in,
WrittenFile, and Reader::open_partition_bytes flow, but parameterize the records
so the reconstruction path is property-tested as required for the Parquet
writer. While touching the open_partition_bytes_rejects_a_mismatched_partition
test, correct the RFC0009.5 typo in the comment.
🪄 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: 380b7a79-bac2-4692-945c-abfce44b0580
📒 Files selected for processing (2)
crates/ourios-parquet/src/reader.rscrates/ourios-parquet/src/writer.rs
CodeRabbit: lead the open_partition_bytes validation note with RFC 0005 §3.9 (keeping RFC0009.5 as the compaction-abort scenario), mention open_partition_bytes in read_all's validation-entry-points doc, and update the final_path() accessor doc to reflect store-backed semantics (object key as a path; address store-backed objects by WrittenFile::key). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot: open_in works with a local Store too, so final_path's 'no local root' note was wrong (it always renders the key as a path there); and read_all's PartitionMismatch bullet now lists open_partition_bytes alongside open_partition. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(RFC 0019 slice 2d) (#299) * feat(parquet): migrate audit sink onto Store buffer-and-put (rfc0019 2d) The compaction audit sink was the last local-only data seam: `AuditWriter` wrote to `<uuid>.parquet.tmp` and `fs::rename`d into place, which S3 can't do. Rework it to buffer-and-put through `ourios_parquet::Store`, mirroring the data `Writer` (PR #294), so audit events are durably written to local *or* S3. - `AuditWriter`: `inner` is `ArrowWriter<Vec<u8>>`; carries `store` + `key`; `open` is the local ctor (delegates to `open_in`, overrides `final_path` to the absolute landing path), `open_in` is the S3-capable ctor; `close` puts the finished bytes via `store.put_blocking`. Removed the temp-file `Drop` cleanup — an abandoned writer just drops its in-memory buffer. `num_rows` is now tracked per sub-batch (into_inner returns bytes, not metadata). - `ParquetAuditSink` takes a `Store` instead of a `bucket_root`; `try_write` uses `AuditWriter::open_in`. - `ourios-server` wires the sink on both backends (clone the preflight store for audit before moving it into `Compactor::new`); dropped the s3 no-op branch and the gap notice. - Test call sites construct `ParquetAuditSink::new(Store::local(..))`; assertions unchanged. Added an `#[ignore]`d localstack test proving durable audit-on-S3 through the sink + `AuditReader`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ingester): emit compaction audit events on the blocking pool Copilot: with the audit sink now wired for S3 (slice 2d), its emit performs blocking store puts (network I/O). The sweep already runs via spawn_blocking, but audit emission ran on the async task, so slow/unavailable S3 could stall the Tokio runtime. Move the audit sink into the sweep's blocking task (it's Send; passed in and returned each iteration) so emission runs off the runtime. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(parquet): unit-test AuditWriter store contract + neutral put error CodeRabbit: add adjacent unit coverage for the new AuditWriter contract and use backend-neutral error text. Adds open_in_publishes_on_close_and_round_trips (buffer-and-put + read-back via the store) and drop_without_close_publishes_nothing (nothing published until close); rewords the close() put-failure Display from 'filesystem I/O' to 'storage I/O' (the put can be S3 now). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(ingester): destructure self up front in Compactor::run Per review: destructure self into owned locals (store/policy/interval/audit_sink) at the top of run() instead of partially moving audit_sink out and then reading the remaining fields. Same behavior; clearer, and avoids the partial-move-of-self pattern. (policy is Copy, so the move closure copies it each loop.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(parquet): correct audit test comment (AuditWrittenFile, key via path) Copilot: the test doc said WrittenFile + a key field; this module returns AuditWrittenFile and carries the store key via its path. Reword. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
) The s3-integration job's --exact list was pinned to the original four RFC 0013 tests, so the Store-migration localstack tests added since (querier/compactor/ audit Store listing/delete/CAS/audit-sink) compiled but never actually ran in CI. Add the six new ones to the list (still --exact, so the deferred rfc0013_8 todo! stub stays un-run). Also: fix a stale Writer::Poisoned message that still referenced a .parquet.tmp on disk (the writer went buffer-and-put in #294). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
What
The foundational data-seam piece of the RFC 0019 compactor migration (slice 2b). The compactor must read inputs and write the consolidated file through a configured
Store(local or S3), butWriter::open/Reader::open_partitionconstructStore::localinternally. This adds the S3-capable seam additively — the live ingest path (RecordSink) is already onStore; this brings the compactor's writer/reader along.Writer::open_in(store: &Store, partition)(+open_in_with_zstd_level) — opens a writer on an already-builtStore.Writer::open(bucket_root, partition)now delegates to it after building the local store, so the ~40 localWriter::open(path, …)call sites are untouched (the no-churn precedent from feat(querier): migrate the audit + scan read paths onto Store (rfc0019 2a) #292'sQuerier::new).WrittenFilegainskey+bytes_written— the backend-agnostic address and size, so the compactor can name the consolidated file and report bytes without stat-ing a localpath(which an S3 writer can't do). The existingpathfield stays (absolute for local; the key rendered as a path for the store ctor).Reader::open_partition_bytes(bytes, partition)— the store-backed counterpart ofopen_partition(read viaStore::get_blocking→ these bytes), keeping the RFC 0005 §3.9 row-vs-path validation so a mis-partitioned compaction input still aborts (RFC0009.5).Why
Independently-mergeable prereq for PR B (compaction.rs + compactor.rs + main.rs onto
Store), so that data compaction can target S3. Pairs with #293 (Storelist_with_sizes_blocking/delete_blocking).Invariants
open_partition_bytes_rejects_a_mismatched_partition).put.Tests
open_in_writes_through_a_store_and_reports_key_and_size(store round-trip,key/bytes_writtencorrectness) andopen_partition_bytes_rejects_a_mismatched_partition(§3.9 abort). All existingourios-parquetsuites pass unchanged.Local gate
cargo fmt --all --check,cargo clippy -p ourios-parquet --all-targets --all-features -D warnings,RUSTDOCFLAGS=-D warnings cargo doc -p ourios-parquet,cargo test -p ourios-parquet,cargo check --workspace --all-targets— all green.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation