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

   > **Stacked on #678 — review that first.** This targets `main`, so it 
currently shows #678's 8 commits alongside its own 3. The delta that belongs to 
this PR is 5 files, +234/-39: the last three commits. It will rebase down to 
just those once #678 merges.
   
   ## Description
   
   Pass 1 read every log file whole, so every block was fetched and fully 
Arrow-decoded before the five gates chose which ones to keep. Two consequences: 
a block that could not decode failed the entire read even when its instant was 
rolled back, uncommitted, or outside the requested range, and a slice's decoded 
batches all sat in memory at once, where the merge map they feed spills but 
this intermediate `Vec` does not.
   
   The header-only tier that avoids both already existed with no production 
caller. `LogFileReader::new_streaming` and `read_all_blocks_metadata_only` were 
reachable only from the memory bench, and 
`Storage::get_streaming_storage_reader` only from `new_streaming` itself. Pass 
1 now uses it, which is also what Java does (`tryReadContent(readLazily = 
true)` seeks past the content) and what the internal reader already runs in 
production.
   
   Passes 1 and 2 read nothing but a block's header, so the range stays 
unbounded and the gates alone decide, and the sweep still sees every rollback 
command block: `instant_time()` and `target_instant_time()` are both header 
lookups. Pass 3 already loaded a deferred block's content through a decoder 
configured with the same row filter and reader schema the eager sweep used, so 
an inflated block is byte-identical to an eagerly read one.
   
   Two robustness fixes ported from the internal reader come with it. A ranged 
read whose end runs past the file is clamped rather than refused 
(`object_store`'s `GetRange::as_range` returns `start..len`), so an overlong 
content length came back as a short buffer and failed with "failed to fill 
whole buffer" from inside the decoder; it now names the truncation. And a 
loaded block releases the location it needed to fetch itself, except when the 
decode produced no content, since a command block decodes to `Empty` and 
releasing there would make a second load fail rather than no-op.
   
   One behavior change worth noting: `hoodie.memory.dfs.buffer.max.size` was 
previously inert, because nothing reached the tier it configures. It now takes 
effect.
   
   ## How are the changes test-covered
   
   - [ ] N/A
   - [x] Automated tests (unit and/or integration tests)
   - [x] Manual tests
     - [x] Details are described below
   
   `test_undecodable_block_in_a_discarded_instant_does_not_fail_the_read`, with 
an admitted-block control so it cannot pass because the bytes still decode. 
Reverting Pass 1 to the eager reader fails it with `Invalid log block version: 
4294967295`, the corrupted bytes the fixture writes. 
`test_load_content_reports_a_block_running_past_the_file_end` and 
`test_load_content_is_idempotent_for_every_block_type` cover the two robustness 
fixes, and each was mutation-checked the same way. #678's uncommitted-log 
fixtures run against this path on both reader versions, and the streaming read 
is covered by 
`test_stream_merges_a_slice_incrementally_and_matches_the_eager_read` on a 
merge-on-read fixture.
   
   The memory claim is measured rather than argued, using the bench already in 
the tree (`HUDI_BENCH=eager|lazy ... --lib memory_bench -- --ignored`). On a 91 
MB log file decoding 1.2M rows, peak resident memory over baseline goes from 
+200 MB to +40 MB.
   


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