Skip to content

[rust] Wire batch statistics into the log batch write and read paths - #4074

Open
leekeiabstraction wants to merge 3 commits into
apache:mainfrom
leekeiabstraction:rust-batch-stats-wiring
Open

[rust] Wire batch statistics into the log batch write and read paths#4074
leekeiabstraction wants to merge 3 commits into
apache:mainfrom
leekeiabstraction:rust-batch-stats-wiring

Conversation

@leekeiabstraction

@leekeiabstraction leekeiabstraction commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Wires the V1 batch statistics collection ([rust] Add V1 batch statistics collection #4011) into the write and read paths. Closes [rust] Batch statistics read wiring #4009, closes [rust] Batch statistics write wiring #4010.
  • Write: tables with table.statistics.columns set emit V1 batches carrying the serialized statistics between the header and the records data, mirroring Java's MemoryLogRecordsArrowBuilder. A serialization failure degrades to an empty statistics section rather than failing the batch.
  • Read: all three magic versions decode — V1 skips the statistics section, V2 additionally reads the leader epoch and shifts the post-CRC header fields, and unknown magics are rejected. This fixes the Rust reader misparsing the V1 batches that Java clients produce when statistics are configured; V2 support is read-side forward-compatibility matching Java's DefaultLogRecordBatch, which also reads V2 while no builder writes it yet. The writer stays on V0/V1 like Java's client-side builder.
  • Refactoring: the Fluss batch framing moved out of arrow.rs into a new log_record_batch.rs, separating the Fluss wire format from Arrow IPC concerns. The Arrow builder's long argument lists collapsed into an ArrowBatchConfig struct.

Test Plan

  • A cross-language golden fixture (fluss-common/src/test/resources/encoding/statistics_block.hex) pins the statistics block byte-for-byte across all supported types, nulls, both decimal widths, both timestamp precisions, and float NaN/signed-zero ordering (bounds follow Java's Float.compare semantics, not Arrow's IEEE totalOrder). A new Java test generates it, asserts it and parses it back verifying the decoded bounds; the Rust test reads the same file.
  • New unit tests cover V1/V2 encode/decode round trips (append-only and changelog), empty, corrupt and truncated batches, leader epoch reads, unsupported magic, per-column degradation for unsupported column types and the size reserve. cargo test -p fluss-rs --lib passes 707 tests; clippy and fmt are clean; the end-to-end integration test is left to [rust] Filter pushdown integration test #3845.

🤖 AI-assisted changes - reviewed by human developer

@leekeiabstraction
leekeiabstraction force-pushed the rust-batch-stats-wiring branch 10 times, most recently from e33bb99 to c09cb91 Compare August 23, 2026 16:19
Tables with 'table.statistics.columns' set now produce V1 batches carrying
serialized statistics, matching Java's MemoryLogRecordsArrowBuilder. The
reader now locates the records data per the batch's magic, fixing the
misparsing of V1 batches that Java clients and the server's projection
path already produce.
@leekeiabstraction

Copy link
Copy Markdown
Contributor Author

@fresh-borzoni @platinumhamburg @charlesdong1991 Appreciate a review here 🙏

@fresh-borzoni fresh-borzoni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@leekeiabstraction Thank you for the great PR, I verified the roundtrip as well 👍
Really great work, LGTM overall, left minor comments, PTAL

@@ -1 +1 @@
// Licensed to the Apache Software Foundation (ASF) under one

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This module grows another export while staying pub. Make it pub(crate) now?


/// The epoch of the leader that wrote this batch, or [`NO_LEADER_EPOCH`]
/// for magics before V2.
pub fn leader_epoch(&self) -> i32 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Which code produces V2 today?
ArrowWalBuilder passes a null collector so CDC stays V0, CURRENT_LOG_MAGIC_VALUE is still V0, and the only V2 branches I find are dormant ones in the two builders.

Did I miss a producer? Asking because the description says the server already produces them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well caught, description is wrong. The code that allows setting of leader epoch is there but nothing calls it yet. Updated descriptions / comments

cursor.write_all(&statistics_bytes)?;
cursor.write_all(real_arrow_batch_bytes)?;

let calcute_crc_bytes = &cursor.get_ref()[SCHEMA_ID_OFFSET..];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: the CRC is read and written at V0/V1 offsets while the reader shifts them for V2.
Add a debug_assert!(magic < LOG_MAGIC_VALUE_V2) so a future V2 writer fails loudly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added debug_assert!

/// The Java reference block, generated and asserted by
/// `LogRecordBatchStatisticsCompatibilityTest` against the same checked-in
/// fixture so both languages pin to one set of bytes.
fn java_statistics_block_hex() -> String {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: this resolves only inside the monorepo, so the test panics in a vendored crate.
Is the published crate expected to pass its own tests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well caught, added .hex file within the crate. Added assertion to check that Rust side .hex file's content is equivalent to Java side when Java side .hex file is accessible.

The log batch framing and the aligned row module are now crate-private, dropping two dead items the tightening surfaced. The golden statistics fixture is embedded in the crate so the test passes outside the monorepo, and a debug assert guards the builder's V0/V1 header layout against a future V2 writer.
@leekeiabstraction

Copy link
Copy Markdown
Contributor Author

TY for the thorough review as usual @fresh-borzoni , addressed your comments. PTAL 🙏

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.

[rust] Batch statistics write wiring [rust] Batch statistics read wiring

2 participants