This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 6d3e2e6898 [mosaic] Size the writer's Arrow vectors by 
write.batch-size (#9741)
6d3e2e6898 is described below

commit 6d3e2e6898dda197dd356b65c99324119c3a4c9a
Author: Dapeng Sun(孙大鹏) <[email protected]>
AuthorDate: Fri Sep 11 17:47:25 2026 +0800

    [mosaic] Size the writer's Arrow vectors by write.batch-size (#9741)
---
 .../paimon/format/mosaic/MosaicRecordsWriter.java  |  19 +++
 .../format/mosaic/MosaicRecordsWriterTest.java     | 136 +++++++++++++++++++++
 2 files changed, 155 insertions(+)

diff --git 
a/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicRecordsWriter.java
 
b/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicRecordsWriter.java
index 1a3e4d4149..f4a96937ce 100644
--- 
a/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicRecordsWriter.java
+++ 
b/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicRecordsWriter.java
@@ -33,7 +33,10 @@ import org.apache.paimon.types.RowType;
 
 import org.apache.arrow.memory.BufferAllocator;
 import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.BaseValueVector;
+import org.apache.arrow.vector.FieldVector;
 import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.complex.BaseRepeatedValueVector;
 import org.apache.arrow.vector.types.pojo.Schema;
 
 import javax.annotation.Nullable;
@@ -101,6 +104,13 @@ public class MosaicRecordsWriter implements 
BundleFormatWriter {
             createdArrowWriter =
                     ArrowFormatWriter.forBorrowedAllocator(
                             rowType, writeBatchSize, true, allocator, 
writeBatchMemory);
+            // Only batches smaller than Arrow's default allocation are sized 
by the batch.
+            if (writeBatchSize < BaseValueVector.INITIAL_VALUE_ALLOCATION) {
+                for (FieldVector vector :
+                        
createdArrowWriter.getVectorSchemaRoot().getFieldVectors()) {
+                    setInitialCapacity(vector, writeBatchSize);
+                }
+            }
             Schema arrowSchema = 
createdArrowWriter.getVectorSchemaRoot().getSchema();
             createdNativeWriter =
                     nativeWriterFactory.create(outputStream, arrowSchema, 
options, allocator);
@@ -113,6 +123,15 @@ public class MosaicRecordsWriter implements 
BundleFormatWriter {
         this.nativeWriter = createdNativeWriter;
     }
 
+    private static void setInitialCapacity(FieldVector vector, int capacity) {
+        if (vector instanceof BaseRepeatedValueVector) {
+            // The plain overload would size the element vector for 5 elements 
per row.
+            ((BaseRepeatedValueVector) vector).setInitialCapacity(capacity, 
1.0);
+        } else {
+            vector.setInitialCapacity(capacity);
+        }
+    }
+
     @Override
     public void addElement(InternalRow internalRow) {
         if (!arrowFormatWriter.write(internalRow)) {
diff --git 
a/paimon-mosaic/src/test/java/org/apache/paimon/format/mosaic/MosaicRecordsWriterTest.java
 
b/paimon-mosaic/src/test/java/org/apache/paimon/format/mosaic/MosaicRecordsWriterTest.java
index 344d5a0022..be69fb6207 100644
--- 
a/paimon-mosaic/src/test/java/org/apache/paimon/format/mosaic/MosaicRecordsWriterTest.java
+++ 
b/paimon-mosaic/src/test/java/org/apache/paimon/format/mosaic/MosaicRecordsWriterTest.java
@@ -20,8 +20,13 @@ package org.apache.paimon.format.mosaic;
 
 import org.apache.paimon.arrow.ArrowBundleRecords;
 import org.apache.paimon.arrow.ArrowUtils;
+import org.apache.paimon.arrow.vector.ArrowFormatWriter;
+import org.apache.paimon.data.BinaryString;
+import org.apache.paimon.data.GenericArray;
+import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.format.FileFormatFactory;
 import org.apache.paimon.mosaic.MosaicWriter;
+import org.apache.paimon.options.MemorySize;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.RowType;
@@ -157,6 +162,137 @@ class MosaicRecordsWriterTest {
         verify(nativeWriter).write(any(VectorSchemaRoot.class));
     }
 
+    @Test
+    void testVectorsAreSizedByWriteBatchSize() throws Exception {
+        // 2,000 columns: Arrow's default per-vector allocation would exceed 
60 MB here.
+        RowType.Builder builder = RowType.builder();
+        for (int i = 0; i < 2000; i++) {
+            builder.field("c" + i, DataTypes.DOUBLE());
+        }
+        RowType wideType = builder.build();
+        MosaicWriter nativeWriter = mock(MosaicWriter.class);
+        try (RootAllocator allocator = new RootAllocator()) {
+            MosaicRecordsWriter writer =
+                    new MosaicRecordsWriter(
+                            new ByteArrayOutputStream(),
+                            wideType,
+                            new FileFormatFactory.FormatContext(new Options(), 
1024, 4),
+                            Collections.emptyList(),
+                            null,
+                            allocator,
+                            (outputStream, arrowSchema, options, 
bufferAllocator) -> nativeWriter);
+            GenericRow row = new GenericRow(wideType.getFieldCount());
+            row.setField(0, 1.0d);
+            writer.addElement(row);
+            assertThat(allocator.getAllocatedMemory()).isLessThan(8L * 1024 * 
1024);
+            writer.close();
+        }
+    }
+
+    @Test
+    void testLargeBatchSizeWithSmallMemoryBudgetKeepsArrowDefaultAllocation() 
throws Exception {
+        // write.batch-size=65536 with write.batch-memory=128 KiB: the memory 
budget flushes long
+        // before the row limit, so the first row must not allocate for the 
whole batch size.
+        RowType rowType = mixedRowType(100);
+        MosaicWriter nativeWriter = mock(MosaicWriter.class);
+        try (RootAllocator allocator = new RootAllocator(16L * 1024 * 1024)) {
+            MosaicRecordsWriter writer =
+                    createWriter(
+                            rowType, allocator, nativeWriter, 65536, 
MemorySize.ofKibiBytes(128));
+            writer.addElement(firstRow(rowType));
+            assertThat(allocator.getAllocatedMemory())
+                    .isEqualTo(baselineFirstRowAllocation(rowType, 65536));
+            writer.close();
+        }
+    }
+
+    @Test
+    void testInitialCapacityNeverExceedsArrowDefaultAllocation() throws 
Exception {
+        // DOUBLE, STRING and ARRAY<DOUBLE> columns cover the fixed-width, 
variable-width and
+        // repeated vector families, whose default sizing differs.
+        RowType rowType = mixedRowType(60);
+        for (int batchSize : new int[] {4, 1024, 3969, 3970, 65536}) {
+            long baseline = baselineFirstRowAllocation(rowType, batchSize);
+            MosaicWriter nativeWriter = mock(MosaicWriter.class);
+            try (RootAllocator allocator = new RootAllocator()) {
+                MosaicRecordsWriter writer =
+                        createWriter(
+                                rowType,
+                                allocator,
+                                nativeWriter,
+                                batchSize,
+                                MemorySize.VALUE_128_MB);
+                try {
+                    writer.addElement(firstRow(rowType));
+                    assertThat(allocator.getAllocatedMemory())
+                            .as("batch size %d", batchSize)
+                            .isLessThanOrEqualTo(baseline);
+                    // Arrow rounds buffers to powers of two, so only clearly 
smaller batches
+                    // allocate less.
+                    if (batchSize <= 1024) {
+                        assertThat(allocator.getAllocatedMemory())
+                                .as("batch size %d", batchSize)
+                                .isLessThan(baseline);
+                    }
+                } finally {
+                    writer.close();
+                }
+            }
+        }
+    }
+
+    private static RowType mixedRowType(int columnsPerType) {
+        RowType.Builder builder = RowType.builder();
+        for (int i = 0; i < columnsPerType; i++) {
+            builder.field("d" + i, DataTypes.DOUBLE());
+            builder.field("s" + i, DataTypes.STRING());
+            builder.field("a" + i, DataTypes.ARRAY(DataTypes.DOUBLE()));
+        }
+        return builder.build();
+    }
+
+    private static GenericRow firstRow(RowType rowType) {
+        GenericRow row = new GenericRow(rowType.getFieldCount());
+        row.setField(0, 1.0d);
+        row.setField(1, BinaryString.fromString("one"));
+        row.setField(2, new GenericArray(new Object[] {1.0d}));
+        return row;
+    }
+
+    /** First-row allocation of the same writer without the capacity loop. */
+    private static long baselineFirstRowAllocation(RowType rowType, int 
batchSize) {
+        try (RootAllocator allocator = new RootAllocator()) {
+            ArrowFormatWriter writer =
+                    ArrowFormatWriter.forBorrowedAllocator(
+                            rowType,
+                            batchSize,
+                            true,
+                            allocator,
+                            MemorySize.VALUE_128_MB.getBytes());
+            assertThat(writer.write(firstRow(rowType))).isTrue();
+            long allocated = allocator.getAllocatedMemory();
+            writer.close();
+            return allocated;
+        }
+    }
+
+    private static MosaicRecordsWriter createWriter(
+            RowType rowType,
+            RootAllocator allocator,
+            MosaicWriter nativeWriter,
+            int writeBatchSize,
+            MemorySize writeBatchMemory) {
+        return new MosaicRecordsWriter(
+                new ByteArrayOutputStream(),
+                rowType,
+                new FileFormatFactory.FormatContext(
+                        new Options(), 1024, writeBatchSize, writeBatchMemory),
+                Collections.emptyList(),
+                null,
+                allocator,
+                (outputStream, arrowSchema, options, bufferAllocator) -> 
nativeWriter);
+    }
+
     private static MosaicRecordsWriter createWriter(
             RowType rowType, RootAllocator allocator, MosaicWriter 
nativeWriter) {
         return new MosaicRecordsWriter(

Reply via email to