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

   ## Description
   
   Independent of the #691 to #696 stack: based on `main`, touching only 
`crates/core/src/hfile/key.rs`.
   
   Parsing one HFile key-value copied the entire uncompressed data block twice, 
once into the `KeyValue` and once into the `Key` inside it. The buffer handed 
to both is the block, not the record, so parsing a block of N records copied it 
2N times and iterating a block cost records times block size. Measured on 
generated blocks holding one data block each, per-record cost was 7.83us at 200 
records, 16.06us at 1,000 and 66.15us at 5,000: growing with the block rather 
than staying flat.
   
   Both structures index everything relative to their `offset`, so each can 
instead hold the bytes it actually spans with the offset at zero, leaving every 
accessor's arithmetic as it was. Per-record cost is then 0.16us, 0.16us and 
0.12us at those three sizes, and iterating a block is 49 to 531 times faster 
across that range. The flatness matters more than the multiple: a fix aimed at 
the wrong cause would have moved the level without changing the shape.
   
   Two behaviour notes, both stated because they are not visible from the diff.
   
   `Key::content` now bounds its slice against the key's own bytes rather than 
against the rest of the block. On corrupt input where the inner length prefix 
claims more content than the key holds, it yields empty instead of reading on 
into the value that follows. A well-formed HFile cannot reach that, since a key 
length is `2 + row + 1 + family + qualifier + 9` and so always covers its 
content. Every other accessor is unchanged, checked by walking all 104,200 
records of the nine fixtures and comparing against the previous implementation 
field by field.
   
   `Key::content_offset` becomes private. It is an offset into the key's own 
bytes, so pairing it with a block buffer, which used to be correct, would now 
index the wrong place. Nothing outside the module used it. `Key::bytes` stays 
public but its meaning narrows from the enclosing block to the key itself; no 
caller in `crates/`, `python/` or `cpp/` reads it.
   
   Lengths come straight out of the file and nothing upstream validates them, 
so the new slices clamp to the buffer. Corrupt lengths panic in the same places 
with the same messages as before rather than becoming silently wrong output.
   
   While reviewing this, a pre-existing bug turned up in the same file and is 
filed separately rather than fixed here: `find_block_for_key` builds its search 
key with `Key::from_bytes` from a raw lookup string with no length prefix, so 
the block-index range query never resolves and every seek degrades to a linear 
block walk. Results stay correct, which is why nothing caught it. Both `Key` 
construction sites involved are byte-identical before and after this change.
   
   ## How are the changes test-covered
   
   - [ ] N/A
   - [x] Automated tests (unit and/or integration tests)
   - [ ] Manual tests
     - [ ] Details are described below
   
   One test added, and it is the one the existing suite lacked. Every other 
test in `key.rs` parses at offset 0, where a record and the buffer it came from 
are nearly the same thing, so none of them would notice a parse that ignored 
the offset or sliced from the start of the buffer. The new test lays two 
records out as a data block does, parses the second at the offset the block 
iterator would advance to, and asserts it reads its own key and its own value. 
It then asserts the narrowing itself: the key holds exactly `key_length` bytes, 
the record holds exactly header plus key plus value, and less than the whole 
block.
   
   That last group is what guards the regression, and it does so without timing 
anything, so it cannot be flaky. Reverting the change fails it with "a parsed 
key must hold exactly its own bytes, not the block's".
   
   Equivalence was checked more broadly than the new test covers: a throwaway 
differential harness reimplemented the previous semantics side by side and 
compared `content`, `content_length`, `value`, `record_size`, `key_length` and 
`value_length` for every record of every data block of all nine fixtures, 
across GZ and NONE codecs, two and three level indexes, shortened first keys 
and non-unique keys. 104,200 records, no mismatches. Six corrupt inputs were 
run against both implementations and panic identically.
   
   A timing benchmark was written for this and deliberately not included: it 
needs a directory of generated blocks too large to commit, so it could never 
fail in CI and carried no signal. The measurements above are reproducible from 
the generator described in the ticket.
   
   Run locally: 1300 pass with default features, 1280 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. No CI run has happened: fork pull 
requests sit at `action_required` until a committer approves them.
   


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