linliu-code opened a new pull request, #696:
URL: https://github.com/apache/hudi-rs/pull/696

   ## Description
   
   **Stacked on #695, which is stacked on #694, #693, #692 and #691. Review 
those first.** Until they merge the diff here shows their commits too; the 
delta is `eede8d9..HEAD`, ten files.
   
   A log block whose record count exceeds the decoder's batch size fails to 
decode at all: `Offsets must be non-negative and within the length of the 
Array`. The batch size is 1024 and a metadata table's log blocks exceed that 
routinely, so the version two metadata read cannot handle a realistic metadata 
table. Nothing in the test corpus saw it, because every metadata fixture block 
holds two or three records and the largest data fixture of any kind is 458 KB.
   
   The trigger is any Avro union with two or more non-null branches, which 
Arrow represents as a dense union, and which the metadata record schema always 
carries in its column statistics. Unions spelled `["null", T]` are unaffected: 
Arrow makes those a nullable `T`, with no offsets buffer. `arrow-avro`'s union 
decoder drains its offsets on flush but keeps the per-branch counts that 
produced them, so the second batch emits offsets past children it has just 
emptied. Reported upstream as apache/arrow-rs#10876 with a three-line 
reproducer. Its decoder exposes no reset, so a clean one has to be built, which 
now happens after a mid-block flush. A block that fits inside one batch never 
reaches that path and pays nothing.
   
   Rebuilding per batch would otherwise repeat schema-sized work, so the writer 
schema's parse and fingerprint are cached and reused across blocks. Measured on 
the metadata record schema, which is 7,969 bytes of JSON, that registration is 
234us of the roughly 400us a construction costs. What remains is the two parses 
inside `arrow-avro`'s own decoder construction, which its public API gives no 
way to skip.
   
   Separately, the header walk now keeps a block's content when its fetch 
window already covered it, instead of seeking past and re-requesting the same 
bytes in pass three. That is one request per log file rather than two, matching 
what the eager reader has always cost. The bytes are copied rather than sliced, 
and charged against a budget of one window shared across the whole scan: a 
`Bytes` slice pins its entire window allocation, and the walk accumulates 
blocks from every log file before the gates run, so residency would otherwise 
grow with the slice instead of staying at the bound the headers-only walk 
exists to buy.
   
   Stage timings move from milliseconds to microseconds. A metadata read 
completes in about a millisecond, so every stage rounded to zero and the 
counters said nothing. Four timers that wrapped an empty span are removed: the 
fetch they were written for had moved to pass three, leaving 
`log_block_decode_ms` structurally zero. The log path is now split into fetch, 
decode and upsert, which is what located the cost described above.
   
   ## How are the changes test-covered
   
   - [ ] N/A
   - [x] Automated tests (unit and/or integration tests)
   - [ ] Manual tests
     - [ ] Details are described below
   
   Three additions and two re-derivations.
   
   The decode fix has two tests over a three-line hand-written schema with a 
two-branch union, encoding 100 records at a batch size of 16, so they need no 
fixture and no generated data. One asserts every row returns and that more than 
one batch was actually produced, so it cannot pass vacuously if a batch-size 
change ever makes 100 records fit in one. The other alternates the union's 
branches and checks every row's identity across the boundary, because a corrupt 
offsets buffer returns wrong values rather than only crashing. A companion test 
without a union pins that multi-batch decoding is not broken in general. With 
the rebuild removed, both union tests fail with the upstream error and the 
non-union test still passes.
   
   The one-request-per-log-file change is asserted by request count through a 
store that wraps the real one, not by rows returned: both paths return the same 
records over the same file, which is the point. Inside one window every block 
is resident and the file costs one get; with a 64 byte window every block 
defers and it costs more than one. A separate test pins that the residency 
budget is a bound: a generous budget lets the walk keep content, a zero budget 
keeps none, and a budget below the total keeps less than everything.
   
   Two existing tests changed subject and were re-derived rather than inverted. 
`test_load_content_reports_a_block_running_past_the_file_end` truncated its 
file after the walk, which no longer leaves a ranged read to come back short; 
it now forces deferral with a one byte window and keeps its assertion, plus a 
guard that deferral actually happened. 
`test_prefetch_returns_content_for_every_admitted_block` had become vacuous, 
since both of its blocks were resident and `fetch_window` was never called; it 
now walks with a zero residency budget and asserts nothing is resident, so the 
keying it exists to pin is exercised again.
   
   Run locally: 1382 pass with default features, 1362 with 
`--no-default-features`, clippy clean on both feature sets with warnings 
denied, fmt clean, and `cargo check --workspace --all-targets --all-features` 
clean including the Python and C++ bindings. That last one matters here: 
`HoodieReadStats` is public and seven of its fields were renamed. No CI run has 
happened: fork pull requests sit at `action_required` until a committer 
approves them, so all of the above is local only.
   
   Not covered, and stated rather than implied: the correctness fix makes a 
large block readable, not fast. Decode is superlinear in records per block, 
6.72us each at 200 records against 68.89us at 5,000, which is a separate defect 
this fix made visible rather than caused. It is filed and not addressed here.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to