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

   ## Description
   
   **Stacked on #694, which is stacked on #693, #692 and #691. Review those 
first.** Until they merge the diff here shows their commits too; the delta is 
`8cd7a9a..HEAD`, five files.
   
   There are two metadata-table read paths, and the one production uses never 
touches the version two file group reader: it opens the base file itself, scans 
the log files itself, and folds records with its own merger. So every fix to 
the merge or to the HFile path reaches ordinary reads and not metadata reads, 
and the metadata table stays on machinery that exists only for it.
   
   This adds the metadata layer that would end that, above the version two 
reader and owning every metadata concern: the partition, the record shape, the 
decoded type, the `.` to empty string normalisation a non-partitioned table 
needs. The reader below is handed a slice, a schema and a predicate, which is 
the layering Hudi already has between `HoodieBackedTableMetadata` and its 
generic file group reader. A named-key read pushes the predicate into the base 
file read, which seeks to the blocks that can hold those keys, and into the 
HFile log block read, where a log block's own index says which of its blocks to 
parse. Dropping log records for keys nobody asked for is sound because the 
merge is per key.
   
   **It is not on the read path, and that is the point of this PR being what it 
is.** `MetadataTable::read_files_partition` still calls the existing reader. 
The new layer matches it record for record and value for value, and is **3.4x 
slower end to end** — 4.24 ms against 1.25 ms on a base file plus eight log 
files, measured with both readers in one process. Three causes, none of them 
the layer itself: the ranged base-file open costs a head request and two range 
requests before any data byte, which is pure overhead on a 7.6 KB metadata 
file; version two's log scan walks every log file twice, once for headers and 
once for content, where the older scanner decoded each block as it walked, and 
that is 2.6 ms of the 3.0 ms difference; and the metadata payload merger 
rebuilds an Arrow map array per merged pair where the older merger mutated a 
map in place.
   
   Two of those predate this change, but switching the default metadata read 
onto that path is what would make them matter, so the switch waits. The layer 
lands with its parity tests so the comparison is standing and checkable while 
the cost is worked on, rather than being rewritten later from a description.
   
   Scope is the `files` partition, matching what the existing reader serves. 
The other five metadata partitions, the key to file group routing they need, 
and a valid instant filter are separate work.
   
   ## How are the changes test-covered
   
   - [ ] N/A
   - [x] Automated tests (unit and/or integration tests)
   - [ ] Manual tests
     - [ ] Details are described below
   
   Parity against the existing reader, record for record and value for value: 
file names, sizes and tombstone flags, not key sets. Both readers run in one 
test over one slice, on the `files` partition before its compaction so the 
comparison covers a real base file plus seven log files rather than a base 
alone. A key set comparison would not have been enough; that is how the fold 
bug fixed in #693 stayed hidden behind an earlier key set assertion. A 
named-key read is compared the same way.
   
   A named-key read is also asserted to put fewer records through the merge 
than a full read, measured with the merge map's peak entry count. That number 
is honest about one mechanism and blind to the rest, which is how the 3.4x 
above went unnoticed until it was measured end to end; it is kept because it is 
the only thing that pins the log-side pushdown, and with the predicate withheld 
from the log decoder both reads peak at four and the test fails on that number.
   
   Every rule has a mutation that fails a named test: the named-key filter, the 
record context rebuild, the key normalisation, the all-partitions rewrite, the 
tombstone flag, the size, the null-valued map entry, and the predicate withheld 
from the log decoder. Three of those were **not** caught on the first pass, all 
for one reason: the fixture's metadata table is partitioned and holds no 
tombstones. They are covered by unit tests on constructed batches now, and one 
of them exposed a real divergence rather than a gap, since the file map was 
normalising every key as though it were a partition when in a files record 
those keys are file names.
   
   Three findings from review are fixed here. The row filter built a set once 
instead of scanning the key list per row, which the two other filter sites in 
this crate already did and this one did not. A null map value is omitted rather 
than defaulted, since defaulting invents a zero sized undeleted file and sends 
a read at a path that does not exist. And the per-block record read reuses the 
existing helper rather than carrying a second copy of how a record is built 
from a key value.
   
   The non-partitioned branch, which asks for `.`, is not covered end to end 
and cannot be: no fixture here is a non-partitioned table with a metadata 
table. The normalisation it depends on is unit tested instead, and both places 
say so.
   
   Run locally on both legs: default features and `--no-default-features`, 
clippy clean on both with warnings denied, fmt clean, the ninety case table 
read suite green, 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, so all of 
the above is local only.
   


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