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 14d3dee2f4 [core][vector] Read vectored ranges into caller-provided 
buffers (#8799)
14d3dee2f4 is described below

commit 14d3dee2f40eb67548501ac516e5bdc5b384a1c4
Author: shyjsarah <[email protected]>
AuthorDate: Wed Jul 22 19:06:39 2026 +0800

    [core][vector] Read vectored ranges into caller-provided buffers (#8799)
    
    `NativeVectorGlobalIndexReader` receives destination buffers from the
    native vector index callback. The current vectored-read path allocates a
    second byte array for every range and then copies the data into those
    destination buffers.
---
 .../main/java/org/apache/paimon/fs/FileRange.java  | 28 ++++++++++++++++++++++
 .../org/apache/paimon/fs/VectoredReadUtils.java    | 15 ++++++++----
 .../apache/paimon/fs/VectoredReadUtilsTest.java    | 21 ++++++++++++++++
 .../index/NativeVectorGlobalIndexReader.java       |  7 +++---
 .../index/SeekableStreamVectorIndexInputTest.java  |  8 +++++++
 5 files changed, 71 insertions(+), 8 deletions(-)

diff --git a/paimon-common/src/main/java/org/apache/paimon/fs/FileRange.java 
b/paimon-common/src/main/java/org/apache/paimon/fs/FileRange.java
index 2c85ae5006..be2869ef5e 100644
--- a/paimon-common/src/main/java/org/apache/paimon/fs/FileRange.java
+++ b/paimon-common/src/main/java/org/apache/paimon/fs/FileRange.java
@@ -20,6 +20,8 @@ package org.apache.paimon.fs;
 
 import java.util.concurrent.CompletableFuture;
 
+import static java.util.Objects.requireNonNull;
+
 /* This file is based on source code from the Hadoop Project 
(http://hadoop.apache.org/), licensed by the Apache
  * Software Foundation (ASF) under the Apache License, Version 2.0. See the 
NOTICE file distributed with this work for
  * additional information regarding copyright ownership. */
@@ -47,12 +49,24 @@ public interface FileRange {
         return new FileRangeImpl(offset, length);
     }
 
+    /**
+     * Factory method to create a FileRange object backed by a caller-provided 
buffer.
+     *
+     * @param offset starting offset of the range.
+     * @param buffer buffer to store the data for this range.
+     * @return a new instance of FileRangeImpl.
+     */
+    static FileRange createFileRange(long offset, byte[] buffer) {
+        return new FileRangeImpl(offset, buffer);
+    }
+
     /** An implementation for {@link FileRange}. */
     class FileRangeImpl implements FileRange {
 
         private final long offset;
         private final int length;
         private final CompletableFuture<byte[]> reader;
+        private byte[] buffer;
 
         public FileRangeImpl(long offset, int length) {
             this.offset = offset;
@@ -60,6 +74,13 @@ public interface FileRange {
             this.reader = new CompletableFuture<>();
         }
 
+        public FileRangeImpl(long offset, byte[] buffer) {
+            this.offset = offset;
+            this.buffer = requireNonNull(buffer, "buffer is null");
+            this.length = this.buffer.length;
+            this.reader = new CompletableFuture<>();
+        }
+
         @Override
         public String toString() {
             return "range[" + offset + "," + (offset + length) + ")";
@@ -79,5 +100,12 @@ public interface FileRange {
         public CompletableFuture<byte[]> getData() {
             return reader;
         }
+
+        byte[] getOrCreateBuffer() {
+            if (buffer == null) {
+                buffer = new byte[length];
+            }
+            return buffer;
+        }
     }
 }
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/fs/VectoredReadUtils.java 
b/paimon-common/src/main/java/org/apache/paimon/fs/VectoredReadUtils.java
index 8f1551bff9..e0933d76e1 100644
--- a/paimon-common/src/main/java/org/apache/paimon/fs/VectoredReadUtils.java
+++ b/paimon-common/src/main/java/org/apache/paimon/fs/VectoredReadUtils.java
@@ -181,7 +181,7 @@ public class VectoredReadUtils {
     private static void fallbackToReadSequence(
             SeekableInputStream in, List<? extends FileRange> ranges) throws 
IOException {
         for (FileRange range : ranges) {
-            byte[] bytes = new byte[range.getLength()];
+            byte[] bytes = getOrCreateBuffer(range);
             in.seek(range.getOffset());
             IOUtils.readFully(in, bytes);
             range.getData().complete(bytes);
@@ -190,13 +190,13 @@ public class VectoredReadUtils {
 
     private static void readSingleRange(VectoredReadable readable, FileRange 
range) {
         if (range.getLength() == 0) {
-            range.getData().complete(new byte[0]);
+            range.getData().complete(getOrCreateBuffer(range));
             return;
         }
         try {
             long position = range.getOffset();
             int length = range.getLength();
-            byte[] buffer = new byte[length];
+            byte[] buffer = getOrCreateBuffer(range);
             readable.preadFully(position, buffer, 0, length);
             range.getData().complete(buffer);
         } catch (Exception ex) {
@@ -212,7 +212,7 @@ public class VectoredReadUtils {
         }
         long offset = combinedRange.offset;
         for (FileRange fileRange : combinedRange.underlying) {
-            byte[] buffer = new byte[fileRange.getLength()];
+            byte[] buffer = getOrCreateBuffer(fileRange);
             copyMultiBytesToBytes(
                     segments,
                     (int) (fileRange.getOffset() - offset),
@@ -222,6 +222,13 @@ public class VectoredReadUtils {
         }
     }
 
+    private static byte[] getOrCreateBuffer(FileRange range) {
+        if (range instanceof FileRange.FileRangeImpl) {
+            return ((FileRange.FileRangeImpl) range).getOrCreateBuffer();
+        }
+        return new byte[range.getLength()];
+    }
+
     private static void completeFileRangesExceptionally(
             CombinedRange combinedRange, Throwable throwable) {
         for (FileRange fileRange : combinedRange.underlying) {
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/fs/VectoredReadUtilsTest.java 
b/paimon-common/src/test/java/org/apache/paimon/fs/VectoredReadUtilsTest.java
index 1cd5476e63..bd66f3f284 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/fs/VectoredReadUtilsTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/fs/VectoredReadUtilsTest.java
@@ -116,6 +116,27 @@ class VectoredReadUtilsTest {
                         FileRange.createFileRange(7300, 850)));
     }
 
+    @Test
+    public void testReadIntoProvidedBuffers() throws Exception {
+        byte[] first = new byte[60];
+        byte[] second = new byte[90];
+        byte[] third = new byte[200];
+        List<FileRange> ranges =
+                Arrays.asList(
+                        FileRange.createFileRange(0, first),
+                        FileRange.createFileRange(100, second),
+                        FileRange.createFileRange(300, third));
+
+        VectoredReadUtils.readVectored(readable, ranges);
+
+        assertThat(ranges.get(0).getData().get()).isSameAs(first);
+        assertThat(ranges.get(1).getData().get()).isSameAs(second);
+        assertThat(ranges.get(2).getData().get()).isSameAs(third);
+        assertThat(first).isEqualTo(Arrays.copyOfRange(bytes, 0, 60));
+        assertThat(second).isEqualTo(Arrays.copyOfRange(bytes, 100, 190));
+        assertThat(third).isEqualTo(Arrays.copyOfRange(bytes, 300, 500));
+    }
+
     @Test
     public void testRandom() throws Exception {
         List<FileRange> ranges = new ArrayList<>();
diff --git 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexReader.java
 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexReader.java
index 1f0b46fca9..b525fdd226 100644
--- 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexReader.java
+++ 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexReader.java
@@ -419,7 +419,7 @@ public class NativeVectorGlobalIndexReader implements 
GlobalIndexReader {
                 throws IOException {
             List<FileRange> ranges = new ArrayList<>(positions.length);
             for (int i = 0; i < positions.length; i++) {
-                ranges.add(FileRange.createFileRange(positions[i], 
buffers[i].length));
+                ranges.add(FileRange.createFileRange(positions[i], 
buffers[i]));
             }
 
             VectoredReadUtils.ReadOptions options =
@@ -430,9 +430,8 @@ public class NativeVectorGlobalIndexReader implements 
GlobalIndexReader {
                             .withSequentialReadFallback(false);
             VectoredReadUtils.readVectored(readable, ranges, options);
 
-            for (int i = 0; i < ranges.size(); i++) {
-                byte[] bytes = ranges.get(i).getData().join();
-                System.arraycopy(bytes, 0, buffers[i], 0, bytes.length);
+            for (FileRange range : ranges) {
+                range.getData().join();
             }
         }
 
diff --git 
a/paimon-vector/src/test/java/org/apache/paimon/vector/index/SeekableStreamVectorIndexInputTest.java
 
b/paimon-vector/src/test/java/org/apache/paimon/vector/index/SeekableStreamVectorIndexInputTest.java
index 82eeee198e..94c3a4cd2b 100644
--- 
a/paimon-vector/src/test/java/org/apache/paimon/vector/index/SeekableStreamVectorIndexInputTest.java
+++ 
b/paimon-vector/src/test/java/org/apache/paimon/vector/index/SeekableStreamVectorIndexInputTest.java
@@ -24,6 +24,7 @@ import org.apache.paimon.fs.VectoredReadable;
 import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -48,6 +49,10 @@ public class SeekableStreamVectorIndexInputTest {
         assertThat(input.positionReads).hasValue(2);
         assertThat(input.sequentialReads).hasValue(0);
         assertThat(input.maxActiveReads).hasValue(2);
+        assertThat(input.positionReadBuffers.stream().anyMatch(buffer -> 
buffer == buffers[0]))
+                .isTrue();
+        assertThat(input.positionReadBuffers.stream().anyMatch(buffer -> 
buffer == buffers[1]))
+                .isTrue();
     }
 
     @Test
@@ -90,6 +95,8 @@ public class SeekableStreamVectorIndexInputTest {
         private final AtomicInteger positionReads = new AtomicInteger();
         private final AtomicInteger sequentialReads = new AtomicInteger();
         private final AtomicInteger maxActiveReads = new AtomicInteger();
+        private final CopyOnWriteArrayList<byte[]> positionReadBuffers =
+                new CopyOnWriteArrayList<>();
 
         private int position;
 
@@ -129,6 +136,7 @@ public class SeekableStreamVectorIndexInputTest {
 
         @Override
         public int pread(long position, byte[] buffer, int offset, int length) 
throws IOException {
+            positionReadBuffers.add(buffer);
             int active = activeReads.incrementAndGet();
             maxActiveReads.accumulateAndGet(active, Math::max);
             readsStarted.countDown();

Reply via email to