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


##########
paimon-core/src/main/java/org/apache/paimon/append/MultipleBlobFileWriter.java:
##########
@@ -114,9 +115,10 @@ public MultipleBlobFileWriter(
                                     null);
             RollingFileWriterImpl<InternalRow, DataFileMeta> rollingWriter =
                     video
-                            ? new VideoRollingFileWriter<>(writerFactory, 
targetFileSize)
+                            ? new VideoRollingFileWriter<>(
+                                    writerFactory, targetFileSize, 
targetFileRowNum)

Review Comment:
   [P2] Passing targetFileRowNum to VideoRollingFileWriter exposes a 
NULL-boundary bug in its deferred policy. Consecutive SQL NULL video values 
produce currentVideo == nextVideo == null, so after a target sets pendingRoll, 
Objects.equals(null, null) prevents the file from ever closing. The previous 
outer writer closed all writers immediately when its target was reached on a 
NULL group. Please treat NULL as having no protected physical-video group and 
close once rolling is pending; an all-NULL row-cap regression test would cover 
this.



##########
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) {
+            return ranges.stream()
+                    .map(SplitGroup::nonRawConvertibleGroup)

Review Comment:
   [P1] A spanning sidecar is now inserted into every intersecting anchor 
split, but DataSplit.dataEvolutionMergedRowCount() still uses the maximum 
physical file row count in each overlap group. In the new six-row/two-camera 
layout, the three two-row anchor splits each contain a four-row video file, so 
they report 4 + 4 + 4 = 12 logical rows. Core LIMIT pushdown can consequently 
omit required splits, and Flink COUNT(*) pushdown can return 12 instead of 6. 
Please derive the count from the normal anchor range, or carry the effective 
logical range explicitly in the split.



##########
paimon-core/src/main/java/org/apache/paimon/append/DedicatedFormatRollingFileWriter.java:
##########
@@ -372,14 +364,9 @@ public void write(InternalRow row) throws IOException {
             }
             recordCount++;
             currentFileRecordCount++;
-            currentVideoGroup = nextVideoGroup;
 
             if (rollingFile()) {
-                if (nextVideoGroup == null) {
-                    closeCurrentWriter();
-                } else {
-                    pendingGroupAwareRoll = true;
-                }
+                closeNormalAndVectorWriters();

Review Comment:
   [P2] Once blob/video files roll independently here, one sidecar can bridge 
many normal-file ranges. DataEvolutionFileStoreScan.postFilterManifestEntries 
still groups all files with RangeHelper, so that sidecar connects the normal 
ranges into one large stats group. The normal providers then cover only a 
subset of the group and are marked unknown, disabling otherwise selective 
normal-column stats pruning and forcing a whole-video scan. Please compute 
evolution stats per normal anchor range, excluding sidecar extents, and 
deduplicate shared sidecars after filtering.



##########
paimon-python/pypaimon/read/scanner/data_evolution_split_generator.py:
##########
@@ -80,25 +77,43 @@ def weight_func(file_list: List[DataFileMeta]) -> int:
                     group for group in split_by_row_id
                     if self.group_stats_filter.may_match(group)
                 ]
+
+            file_ids = [id(file) for group in split_by_row_id for file in 
group]
+            has_spanning_sidecar = len(file_ids) != len(set(file_ids))
+            if self.group_stats_filter is not None:
                 split_by_row_id = [
                     [file.copy_without_stats() 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
-            )
+            if has_spanning_sidecar:

Review Comment:
   [P2] has_spanning_sidecar is partition-wide, so one spanning sidecar 
bypasses ordered bin packing for every normal range in the partition. A 
100-frame video with target-file-row-num=1 produces 100 splits even when 
target_split_size is large enough to pack them, creating one task per frame 
range and repeatedly opening the same sidecar. Please continue packing normal 
anchor groups and deduplicate shared sidecar metadata within each packed 
DataSplit, or otherwise limit the unpacking to groups that truly cannot be 
combined.



##########
paimon-python/pypaimon/write/writer/dedicated_format_writer.py:
##########
@@ -568,14 +517,37 @@ def pending_row_count(self) -> int:
         return 0
 
     def _close_current_writers(self):
-        """Close normal, blob, and vector writers; add metadata in order: 
normal, blob, vector."""
-        # A flush spans the normal file and every blob/vector sidecar, and the
-        # sidecar writers drain their own buffers as they go, so their half 
cannot
-        # be replayed from scratch. Two rules make a retry resume rather than
-        # restart: the normal rows stay buffered until their file lands, and 
once
-        # it has landed the file is remembered instead of the rows. Nothing
-        # reaches ``committed_files`` until every phase has succeeded, so a 
retry
-        # never finds a half-published flush.
+        """Close normal/vector writers, then finalize independently rolling 
blobs."""
+        self._close_normal_and_vector_writers()
+        if self._blob_writers_closed:

Review Comment:
   [P1] This flag is lifetime-scoped, while StreamTableWrite is reusable across 
prepare_commit calls. After the first prepare sets _blob_writers_closed, later 
writes do not reset it, so the next prepare returns here after publishing the 
new normal/vector metadata and never harvests the new blob/video metadata. A 
write -> prepare -> write -> prepare reproduction committed the second normal 
file but returned NULL for its blob and left the sidecar unreferenced. Please 
make blob finalization checkpoint/epoch-scoped; note that simply resetting the 
flag is insufficient because total_record_count is currently cumulative while 
subwriter metadata is cleared after each harvest.



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