JingsongLi commented on code in PR #9473:
URL: https://github.com/apache/paimon/pull/9473#discussion_r3889253292


##########
paimon-python/pypaimon/read/scanner/data_evolution_split_generator.py:
##########
@@ -80,25 +77,44 @@ def weight_func(file_list: List[DataFileMeta]) -> int:
                     group for group in split_by_row_id
                     if self.group_stats_filter.may_match(group)
                 ]
+
+            has_spanning_sidecar = self._has_spanning_sidecar(split_by_row_id)
+            if self.group_stats_filter is not None:
+                copies = {}
+
+                def without_stats(file):
+                    copy = copies.get(id(file))
+                    if copy is None:
+                        copy = file.copy_without_stats()
+                        copies[id(file)] = copy
+                    return copy
+
                 split_by_row_id = [
-                    [file.copy_without_stats() for file in group]
+                    [without_stats(file) for file in group]
                     for group in split_by_row_id
                 ]
 
-            # Pack the split groups for optimal split sizes
             packed_files = self._pack_for_ordered(
-                split_by_row_id, weight_func, self.target_split_size
+                split_by_row_id,
+                self._normal_group_weight,
+                self.target_split_size,
             )
 
             # Flatten the packed files and build splits
             flatten_packed_files: List[List[DataFileMeta]] = [
-                [file for sub_pack in pack for file in sub_pack]
+                self._unique_files(pack)
                 for pack in packed_files
             ]
 
-            splits += self._build_split_from_pack_for_data_evolution(
+            new_splits = self._build_split_from_pack_for_data_evolution(
                 flatten_packed_files, packed_files, entries_list
             )
+            if has_spanning_sidecar and slice_row_ranges is None and 
self.row_ranges is None:
+                new_splits = [
+                    IndexedSplit(split, self._normal_ranges(pack))

Review Comment:
   [P1] Preserve the DV-aware merged row count in this wrapper. 
IndexedSplit.merged_row_count() falls back to the raw row-range cardinality 
when exact_merged_row_count is omitted. With two one-row anchors, one spanning 
video, and a cardinality-1 deletion vector, this new wrapper reports 2 while 
the underlying DataSplit reports 1; if DV cardinality is unavailable, it 
similarly converts an unknown count into a known physical count. This makes 
split and Ray row-count metadata incorrect. Please carry the underlying exact 
count here and preserve the unknown state, with regressions for both known and 
unknown DV cardinalities.



##########
paimon-python/pypaimon/read/scanner/data_evolution_split_generator.py:
##########
@@ -112,6 +128,62 @@ def weight_func(file_list: List[DataFileMeta]) -> int:
 
         return splits
 
+    def _normal_group_weight(self, group: List[DataFileMeta]) -> int:
+        normal_files = [
+            file for file in group
+            if (not DataFileMeta.is_blob_file(file.file_name)
+                and not DataFileMeta.is_vector_file(file.file_name))
+        ]
+        files = normal_files or group
+        return max(sum(file.file_size for file in files), self.open_file_cost)

Review Comment:
   [P2] Keep distinct sidecar sizes in the packing weight. This removes every 
BLOB or vector file from the weight, not only repeated references to one 
spanning sidecar. For example, ten one-range 64 MiB video files plus tiny 
normal anchors are now packed into one approximately 640 MiB split even with a 
128 MiB target. That makes source.split.target-size ineffective and collapses 
scan parallelism for multi-video data. Please charge each unique sidecar once 
per packed split while avoiding only the duplicate charge for spanning 
references.



##########
paimon-core/src/main/java/org/apache/paimon/table/source/DataEvolutionSplitGenerator.java:
##########
@@ -49,8 +53,15 @@ public boolean alwaysRawConvertible() {
 
     @Override
     public List<SplitGroup> splitForBatch(List<DataFileMeta> input) {
-        RangeHelper<DataFileMeta> rangeHelper = new 
RangeHelper<>(DataFileMeta::nonNullRowIdRange);
-        List<List<DataFileMeta>> ranges = 
rangeHelper.mergeOverlappingRanges(input);
+        List<List<DataFileMeta>> ranges = groupByNormalFileRange(input, 
Function.identity());
+        Set<DataFileMeta> seen = Collections.newSetFromMap(new 
IdentityHashMap<>());
+        boolean hasSpanningSidecar =
+                ranges.stream().flatMap(Collection::stream).anyMatch(file -> 
!seen.add(file));
+        if (hasSpanningSidecar) {

Review Comment:
   [P2] Preserve bin packing when a sidecar spans anchor ranges. This 
partition-wide early return turns every normal anchor into a separate split: 
with target-file-row-num=1, a 100-frame sidecar produces 100 tasks and reopens 
the same video file in each one even when targetSplitSize could pack all 
ranges. Please pack the normal-anchor groups, deduplicate shared sidecar 
metadata within each packed split, and carry the effective anchor ranges to the 
reader.



##########
paimon-python/pypaimon/read/split_read.py:
##########
@@ -1274,49 +1275,26 @@ def _create_prescan_reader(self, field_names):
         return prescan_read._create_raw_reader()
 
     def _split_by_row_id(self, files: List[DataFileMeta]) -> 
List[List[DataFileMeta]]:
-        """Split files by firstRowId for data evolution."""
-
-        # Sort files by firstRowId and then by maxSequenceNumber
-        def sort_key(file: DataFileMeta) -> tuple:
-            first_row_id = file.first_row_id if file.first_row_id is not None 
else float('-inf')
-            is_special = 1 if (DataFileMeta.is_blob_file(file.file_name)
-                               or DataFileMeta.is_vector_file(file.file_name)) 
else 0
-            max_seq = file.max_sequence_number
-            return (first_row_id, is_special, -max_seq)
-
-        sorted_files = sorted(files, key=sort_key)
-
-        # Split files by firstRowId
-        split_by_row_id = []
-        last_row_id = -1
-        check_row_id_start = 0
-        current_split = []
-
-        for file in sorted_files:
-            first_row_id = file.first_row_id
-            if first_row_id is None:
-                split_by_row_id.append([file])
-                continue
-
-            if (not DataFileMeta.is_blob_file(file.file_name)
-                    and not DataFileMeta.is_vector_file(file.file_name)
-                    and first_row_id != last_row_id):
-                if current_split:
-                    split_by_row_id.append(current_split)
-                if first_row_id < check_row_id_start:
-                    raise ValueError(
-                        f"There are overlapping files in the split: {files}, "
-                        f"the wrong file is: {file}"
-                    )
-                current_split = []
-                last_row_id = first_row_id
-                check_row_id_start = first_row_id + file.row_count
-            current_split.append(file)
-
-        if current_split:
-            split_by_row_id.append(current_split)
-
-        return split_by_row_id
+        """Group sidecars by every normal-file range they intersect."""
+        files_without_row_id = [file for file in files if file.first_row_id is 
None]
+        ranged_files = [file for file in files if file.first_row_id is not 
None]
+        groups = [[file] for file in files_without_row_id]
+        groups.extend(group_by_normal_file_range(

Review Comment:
   [P2] Packing and deduplicating metadata does not prevent the sidecar from 
being reopened here. _create_raw_reader later creates one union reader per 
reconstructed normal-range group, and each group constructs a new 
FormatBlobReader for the same file. I reproduced one packed split with 10 
one-row normal files and one video file opening the video reader 10 times. 
Please share one sidecar reader or index across the packed logical ranges, or 
preserve enough grouping metadata to avoid rebuilding it per anchor.



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