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

zhouky pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-celeborn.git


The following commit(s) were added to refs/heads/main by this push:
     new 505ba804c [CELEBORN-752] Support read local shuffle file for spark
505ba804c is described below

commit 505ba804c7d7890922e09aaacd2b155c70328a35
Author: mingji <[email protected]>
AuthorDate: Wed Aug 30 18:52:18 2023 +0800

    [CELEBORN-752] Support read local shuffle file for spark
    
    ### What changes were proposed in this pull request?
    For spark clusters, support read local shuffle file if Celeborn is 
co-deployed with yarn node managers. This PR help to reduce the number of 
active connections.
    
    ### Why are the changes needed?
    Ditto.
    
    ### Does this PR introduce _any_ user-facing change?
    NO.
    
    ### How was this patch tested?
    GA and cluster.  The performance is identical whether you enable local 
reader, but the active connection number may vary according to your connections 
per peer.
    <img width="951" alt="截屏2023-08-16 20 20 14" 
src="https://github.com/apache/incubator-celeborn/assets/4150993/9106e731-28fc-4e78-9c05-ae6a269d249a";>
    The active connection number changed from 3745 to 2894. This PR will help 
to improve cluster stability.
    
    Closes #1812 from FMX/CELEBORN-752.
    
    Authored-by: mingji <[email protected]>
    Signed-off-by: zky.zhoukeyong <[email protected]>
---
 .../org/apache/celeborn/client/ShuffleClient.java  |  22 +-
 .../celeborn/client/read/CelebornInputStream.java  |  38 +++-
 .../celeborn/client/read/DfsPartitionReader.java   |   1 +
 .../celeborn/client/read/LocalPartitionReader.java | 232 +++++++++++++++++++++
 .../client/read/WorkerPartitionReader.java         |   2 +
 .../celeborn/common/util}/FileChannelUtils.java    |   2 +-
 common/src/main/proto/TransportMessages.proto      |   2 +-
 .../org/apache/celeborn/common/CelebornConf.scala  |  17 ++
 .../org/apache/celeborn/common/util/Utils.scala    |   4 +-
 docs/configuration/client.md                       |   2 +
 .../service/deploy/worker/storage/FileWriter.java  |   5 +-
 .../deploy/worker/storage/MapDataPartition.java    |   1 +
 .../worker/storage/MapPartitionFileWriter.java     |  13 +-
 .../worker/storage/ReducePartitionFileWriter.java  |  24 ++-
 .../service/deploy/worker/FetchHandler.scala       |  42 +++-
 .../cluster/ClusterReadWriteTestWithLZ4.scala      |   4 +
 .../cluster/ClusterReadWriteTestWithZSTD.scala     |   4 +
 .../service/deploy/cluster/ReadWriteTestBase.scala |   3 +-
 18 files changed, 374 insertions(+), 44 deletions(-)

diff --git a/client/src/main/java/org/apache/celeborn/client/ShuffleClient.java 
b/client/src/main/java/org/apache/celeborn/client/ShuffleClient.java
index 30f7af8fb..99025e037 100644
--- a/client/src/main/java/org/apache/celeborn/client/ShuffleClient.java
+++ b/client/src/main/java/org/apache/celeborn/client/ShuffleClient.java
@@ -19,6 +19,7 @@ package org.apache.celeborn.client;
 
 import java.io.IOException;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.LongAdder;
 
 import org.apache.hadoop.fs.FileSystem;
 import org.slf4j.Logger;
@@ -37,10 +38,12 @@ import org.apache.celeborn.common.write.PushState;
  * implementation
  */
 public abstract class ShuffleClient {
+  private static Logger logger = LoggerFactory.getLogger(ShuffleClient.class);
   private static volatile ShuffleClient _instance;
   private static volatile boolean initialized = false;
   private static volatile FileSystem hdfsFs;
-  private static Logger logger = LoggerFactory.getLogger(ShuffleClient.class);
+  private static LongAdder totalReadCounter = new LongAdder();
+  private static LongAdder localShuffleReadCounter = new LongAdder();
 
   // for testing
   public static void reset() {
@@ -95,6 +98,23 @@ public abstract class ShuffleClient {
     return hdfsFs;
   }
 
+  public static void incrementLocalReadCounter() {
+    localShuffleReadCounter.increment();
+    totalReadCounter.increment();
+  }
+
+  public static void incrementTotalReadCounter() {
+    totalReadCounter.increment();
+  }
+
+  public static String getReadCounters() {
+    long totalReadCount = totalReadCounter.longValue();
+    long localReadCount = localShuffleReadCounter.longValue();
+    return String.format(
+        "Current client read %d(local)/%d(total) partitions, local ratio %.2f",
+        localReadCount, totalReadCount, (localReadCount * 1.0d / 
totalReadCount) * 100);
+  }
+
   public abstract void setupLifecycleManagerRef(String host, int port);
 
   public abstract void setupLifecycleManagerRef(RpcEndpointRef endpointRef);
diff --git 
a/client/src/main/java/org/apache/celeborn/client/read/CelebornInputStream.java 
b/client/src/main/java/org/apache/celeborn/client/read/CelebornInputStream.java
index 29b4abcee..8645cc4c1 100644
--- 
a/client/src/main/java/org/apache/celeborn/client/read/CelebornInputStream.java
+++ 
b/client/src/main/java/org/apache/celeborn/client/read/CelebornInputStream.java
@@ -31,6 +31,7 @@ import org.roaringbitmap.RoaringBitmap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.celeborn.client.ShuffleClient;
 import org.apache.celeborn.client.compress.Decompressor;
 import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.exception.CelebornIOException;
@@ -129,6 +130,8 @@ public abstract class CelebornInputStream extends 
InputStream {
     private final byte[] sizeBuf = new byte[BATCH_HEADER_SIZE];
     private LongAdder skipCount = new LongAdder();
     private final boolean rangeReadFilter;
+    private final boolean enabledReadLocalShuffle;
+    private final String localHostAddress;
 
     private boolean pushReplicateEnabled;
     private boolean fetchExcludeWorkerOnFailureEnabled;
@@ -156,6 +159,8 @@ public abstract class CelebornInputStream extends 
InputStream {
       this.startMapIndex = startMapIndex;
       this.endMapIndex = endMapIndex;
       this.rangeReadFilter = conf.shuffleRangeReadFilterEnabled();
+      this.enabledReadLocalShuffle = conf.enableReadLocalShuffleFile();
+      this.localHostAddress = Utils.localHostName(conf);
       this.pushReplicateEnabled = conf.clientPushReplicateEnabled();
       this.fetchExcludeWorkerOnFailureEnabled = 
conf.clientFetchExcludeWorkerOnFailureEnabled();
       this.shuffleCompressionEnabled =
@@ -389,20 +394,30 @@ public abstract class CelebornInputStream extends 
InputStream {
         logger.debug("Read peer {} for attempt {}.", location, attemptNumber);
       }
 
-      logger.debug("create reader for location {}", location);
+      logger.debug("Create reader for location {}", location);
 
       StorageInfo storageInfo = location.getStorageInfo();
       if (storageInfo.getType() == StorageInfo.Type.HDD
           || storageInfo.getType() == StorageInfo.Type.SSD) {
-        return new WorkerPartitionReader(
-            conf,
-            shuffleKey,
-            location,
-            clientFactory,
-            startMapIndex,
-            endMapIndex,
-            fetchChunkRetryCnt,
-            fetchChunkMaxRetry);
+        logger.debug(
+            "Read local shuffle file enabled {} , {}, {}",
+            enabledReadLocalShuffle,
+            location.getWorker().host(),
+            localHostAddress);
+        if (enabledReadLocalShuffle && 
location.getWorker().host().equals(localHostAddress)) {
+          return new LocalPartitionReader(
+              conf, shuffleKey, location, clientFactory, startMapIndex, 
endMapIndex);
+        } else {
+          return new WorkerPartitionReader(
+              conf,
+              shuffleKey,
+              location,
+              clientFactory,
+              startMapIndex,
+              endMapIndex,
+              fetchChunkRetryCnt,
+              fetchChunkMaxRetry);
+        }
       }
       if (storageInfo.getType() == StorageInfo.Type.HDFS) {
         return new DfsPartitionReader(
@@ -484,6 +499,9 @@ public abstract class CelebornInputStream extends 
InputStream {
         currentReader.close();
         currentReader = null;
       }
+      if (enabledReadLocalShuffle) {
+        logger.info(ShuffleClient.getReadCounters());
+      }
     }
 
     private boolean moveToNextChunk() throws IOException {
diff --git 
a/client/src/main/java/org/apache/celeborn/client/read/DfsPartitionReader.java 
b/client/src/main/java/org/apache/celeborn/client/read/DfsPartitionReader.java
index d42219773..68f6308b3 100644
--- 
a/client/src/main/java/org/apache/celeborn/client/read/DfsPartitionReader.java
+++ 
b/client/src/main/java/org/apache/celeborn/client/read/DfsPartitionReader.java
@@ -170,6 +170,7 @@ public class DfsPartitionReader implements PartitionReader {
           });
       fetchThread.start();
       logger.debug("Start dfs read on location {}", location);
+      ShuffleClient.incrementTotalReadCounter();
     }
   }
 
diff --git 
a/client/src/main/java/org/apache/celeborn/client/read/LocalPartitionReader.java
 
b/client/src/main/java/org/apache/celeborn/client/read/LocalPartitionReader.java
new file mode 100644
index 000000000..1168c8d2c
--- /dev/null
+++ 
b/client/src/main/java/org/apache/celeborn/client/read/LocalPartitionReader.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.client.read;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.ReferenceCounted;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.celeborn.client.ShuffleClient;
+import org.apache.celeborn.common.CelebornConf;
+import org.apache.celeborn.common.exception.CelebornIOException;
+import org.apache.celeborn.common.network.client.TransportClient;
+import org.apache.celeborn.common.network.client.TransportClientFactory;
+import org.apache.celeborn.common.network.protocol.TransportMessage;
+import org.apache.celeborn.common.protocol.MessageType;
+import org.apache.celeborn.common.protocol.PartitionLocation;
+import org.apache.celeborn.common.protocol.PbOpenStream;
+import org.apache.celeborn.common.protocol.PbStreamHandler;
+import org.apache.celeborn.common.util.FileChannelUtils;
+import org.apache.celeborn.common.util.ThreadUtils;
+
+public class LocalPartitionReader implements PartitionReader {
+
+  private static final Logger logger = 
LoggerFactory.getLogger(LocalPartitionReader.class);
+  private static volatile ThreadPoolExecutor readLocalShufflePool;
+  private final LinkedBlockingQueue<ByteBuf> results;
+  private final AtomicReference<IOException> exception = new 
AtomicReference<>();
+  private final int fetchMaxReqsInFlight;
+  private final PartitionLocation location;
+  private volatile boolean closed = false;
+  private final int numChunks;
+  private int returnedChunks = 0;
+  private int chunkIndex = 0;
+  private final FileChannel shuffleChannel;
+  private List<Long> chunkOffsets;
+  private AtomicBoolean pendingFetchTask = new AtomicBoolean(false);
+
+  public LocalPartitionReader(
+      CelebornConf conf,
+      String shuffleKey,
+      PartitionLocation location,
+      TransportClientFactory clientFactory,
+      int startMapIndex,
+      int endMapIndex)
+      throws IOException {
+    if (readLocalShufflePool == null) {
+      synchronized (LocalPartitionReader.class) {
+        if (readLocalShufflePool == null) {
+          readLocalShufflePool =
+              ThreadUtils.newDaemonCachedThreadPool(
+                  "local-shuffle-reader-thread", 
conf.readLocalShuffleThreads(), 60);
+        }
+      }
+    }
+    fetchMaxReqsInFlight = conf.clientFetchMaxReqsInFlight();
+    results = new LinkedBlockingQueue<>();
+    this.location = location;
+    PbStreamHandler streamHandle;
+    long fetchTimeoutMs = conf.clientFetchTimeoutMs();
+    try {
+      TransportClient client =
+          clientFactory.createClient(location.getHost(), 
location.getFetchPort(), 0);
+      TransportMessage openStreamMsg =
+          new TransportMessage(
+              MessageType.OPEN_STREAM,
+              PbOpenStream.newBuilder()
+                  .setShuffleKey(shuffleKey)
+                  .setFileName(location.getFileName())
+                  .setStartIndex(startMapIndex)
+                  .setEndIndex(endMapIndex)
+                  .setReadLocalShuffle(true)
+                  .build()
+                  .toByteArray());
+      ByteBuffer response = client.sendRpcSync(openStreamMsg.toByteBuffer(), 
fetchTimeoutMs);
+      streamHandle = 
TransportMessage.fromByteBuffer(response).getParsedPayload();
+    } catch (IOException | InterruptedException e) {
+      throw new IOException(
+          "Read shuffle file from local file failed, partition location: "
+              + location
+              + " filePath: "
+              + location.getStorageInfo().getFilePath(),
+          e);
+    }
+
+    chunkOffsets = new ArrayList<>(streamHandle.getChunkOffsetsList());
+    numChunks = streamHandle.getNumChunks();
+    shuffleChannel = 
FileChannelUtils.openReadableFileChannel(streamHandle.getFullPath());
+    if (endMapIndex != Integer.MAX_VALUE) {
+      shuffleChannel.position(chunkOffsets.get(0));
+    }
+
+    logger.debug(
+        "Local partition reader {} offsets:{}",
+        location.getStorageInfo().getFilePath(),
+        StringUtils.join(chunkOffsets, ","));
+
+    ShuffleClient.incrementLocalReadCounter();
+  }
+
+  private void doFetchChunks(int chunkIndex, int toFetch) {
+    try {
+      for (int i = 0; i < toFetch; i++) {
+        long offset = chunkOffsets.get(chunkIndex + i);
+        long length = chunkOffsets.get(chunkIndex + i + 1) - offset;
+        logger.debug("Read {} offset {} length {}", chunkIndex, offset, 
length);
+        // A chunk must be smaller than INT.MAX_VALUE
+        ByteBuffer buffer = ByteBuffer.allocate((int) length);
+        while (buffer.hasRemaining()) {
+          if (-1 == shuffleChannel.read(buffer)) {
+            throw new CelebornIOException(
+                "Read local file " + location.getStorageInfo().getFilePath() + 
" failed");
+          }
+        }
+        buffer.flip();
+        // Avoid resource leak
+        synchronized (this) {
+          if (!closed) {
+            results.put(Unpooled.wrappedBuffer(buffer));
+            logger.debug("Add index {} to results", chunkIndex + i);
+          }
+        }
+      }
+    } catch (InterruptedException e) {
+      // cancel a task for speculative, ignore this exception
+      logger.warn("Read thread is interrupted.", e);
+    } catch (Exception ioe) {
+      logger.error("Read thread encountered error.", ioe);
+      if (ioe instanceof CelebornIOException) {
+        exception.set((IOException) ioe);
+      } else {
+        exception.set(new CelebornIOException("Read thread encountered error", 
ioe));
+      }
+    }
+    pendingFetchTask.compareAndSet(true, false);
+  }
+
+  private void fetchChunks() {
+    int inFlight = chunkIndex - returnedChunks;
+    if (inFlight < fetchMaxReqsInFlight) {
+      int toFetch = Math.min(fetchMaxReqsInFlight - inFlight + 1, numChunks - 
chunkIndex);
+      if (pendingFetchTask.compareAndSet(false, true)) {
+        logger.debug(
+            "Trigger local reader fetch chunk with {} and fetch {} chunks", 
chunkIndex, toFetch);
+        int currentIndex = chunkIndex;
+        readLocalShufflePool.submit(() -> doFetchChunks(currentIndex, 
toFetch));
+        chunkIndex += toFetch;
+      }
+    }
+  }
+
+  @Override
+  public boolean hasNext() {
+    logger.debug("Check has next current index: {} chunks {}", returnedChunks, 
numChunks);
+    return returnedChunks < numChunks;
+  }
+
+  @Override
+  public ByteBuf next() throws IOException, InterruptedException {
+    checkException();
+    if (chunkIndex < numChunks) {
+      fetchChunks();
+    }
+    ByteBuf chunk = null;
+    try {
+      while (chunk == null) {
+        checkException();
+        chunk = results.poll(100, TimeUnit.MILLISECONDS);
+        logger.debug("Poll result with result size: {}", results.size());
+      }
+    } catch (InterruptedException e) {
+      logger.error("PartitionReader thread interrupted while fetching data.");
+      throw e;
+    }
+    returnedChunks++;
+    return chunk;
+  }
+
+  private void checkException() throws IOException {
+    IOException e = exception.get();
+    if (e != null) {
+      throw e;
+    }
+  }
+
+  @Override
+  public void close() {
+    synchronized (this) {
+      closed = true;
+      if (!results.isEmpty()) {
+        results.forEach(ReferenceCounted::release);
+      }
+      results.clear();
+    }
+    try {
+      shuffleChannel.close();
+    } catch (IOException e) {
+      logger.warn("Close local shuffle file failed.", e);
+    }
+  }
+
+  @Override
+  public PartitionLocation getLocation() {
+    return location;
+  }
+}
diff --git 
a/client/src/main/java/org/apache/celeborn/client/read/WorkerPartitionReader.java
 
b/client/src/main/java/org/apache/celeborn/client/read/WorkerPartitionReader.java
index c02c7dd4f..aa73297de 100644
--- 
a/client/src/main/java/org/apache/celeborn/client/read/WorkerPartitionReader.java
+++ 
b/client/src/main/java/org/apache/celeborn/client/read/WorkerPartitionReader.java
@@ -28,6 +28,7 @@ import io.netty.util.ReferenceCounted;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.celeborn.client.ShuffleClient;
 import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.exception.CelebornIOException;
 import org.apache.celeborn.common.network.buffer.ManagedBuffer;
@@ -125,6 +126,7 @@ public class WorkerPartitionReader implements 
PartitionReader {
     this.fetchChunkRetryCnt = fetchChunkRetryCnt;
     this.fetchChunkMaxRetry = fetchChunkMaxRetry;
     testFetch = conf.testFetchFailure();
+    ShuffleClient.incrementTotalReadCounter();
   }
 
   public boolean hasNext() {
diff --git 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/FileChannelUtils.java
 b/common/src/main/java/org/apache/celeborn/common/util/FileChannelUtils.java
similarity index 95%
rename from 
worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/FileChannelUtils.java
rename to 
common/src/main/java/org/apache/celeborn/common/util/FileChannelUtils.java
index 3f08c78d9..ea89a6782 100644
--- 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/FileChannelUtils.java
+++ b/common/src/main/java/org/apache/celeborn/common/util/FileChannelUtils.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.celeborn.service.deploy.worker.storage;
+package org.apache.celeborn.common.util;
 
 import java.io.IOException;
 import java.nio.channels.FileChannel;
diff --git a/common/src/main/proto/TransportMessages.proto 
b/common/src/main/proto/TransportMessages.proto
index b303e6482..4c6ea7cc5 100644
--- a/common/src/main/proto/TransportMessages.proto
+++ b/common/src/main/proto/TransportMessages.proto
@@ -483,7 +483,7 @@ message PbOpenStream {
   int32 startIndex = 3;
   int32 endIndex = 4;
   int32 initialCredit = 5;
-  bool localRead = 6;
+  bool readLocalShuffle = 6;
 }
 
 message PbStreamHandler {
diff --git 
a/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala 
b/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala
index 916d0806b..46e1c88a0 100644
--- a/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala
+++ b/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala
@@ -823,6 +823,8 @@ class CelebornConf(loadDefaults: Boolean) extends Cloneable 
with Logging with Se
     get(CLIENT_BATCH_HANDLE_RELEASE_PARTITION_THREADS)
   def batchHandleReleasePartitionRequestInterval: Long =
     get(CLIENT_BATCH_HANDLED_RELEASE_PARTITION_INTERVAL)
+  def enableReadLocalShuffleFile: Boolean = get(READ_LOCAL_SHUFFLE_FILE)
+  def readLocalShuffleThreads: Int = get(READ_LOCAL_SHUFFLE_THREADS)
 
   // //////////////////////////////////////////////////////
   //                       Worker                        //
@@ -3799,4 +3801,19 @@ object CelebornConf extends Logging {
       .transform(_.toUpperCase(Locale.ROOT))
       .createWithDefault("HDD,SSD")
 
+  val READ_LOCAL_SHUFFLE_FILE: ConfigEntry[Boolean] =
+    buildConf("celeborn.client.readLocalShuffleFile.enabled")
+      .categories("client")
+      .version("0.3.1")
+      .doc("Enable read local shuffle file for clusters that co-deployed with 
yarn node manager.")
+      .booleanConf
+      .createWithDefault(false)
+
+  val READ_LOCAL_SHUFFLE_THREADS: ConfigEntry[Int] =
+    buildConf("celeborn.client.readLocalShuffleFile.threads")
+      .categories("client")
+      .version("0.3.1")
+      .doc("Threads count for read local shuffle file.")
+      .intConf
+      .createWithDefault(4)
 }
diff --git a/common/src/main/scala/org/apache/celeborn/common/util/Utils.scala 
b/common/src/main/scala/org/apache/celeborn/common/util/Utils.scala
index 4b5cfa05a..1b5717bbf 100644
--- a/common/src/main/scala/org/apache/celeborn/common/util/Utils.scala
+++ b/common/src/main/scala/org/apache/celeborn/common/util/Utils.scala
@@ -25,7 +25,7 @@ import java.nio.ByteBuffer
 import java.nio.channels.FileChannel
 import java.nio.charset.StandardCharsets
 import java.util
-import java.util.{Locale, Properties, Random, UUID}
+import java.util.{ArrayList, List, Locale, Properties, Random, UUID}
 import java.util.concurrent.{Callable, ThreadPoolExecutor, TimeoutException, 
TimeUnit}
 
 import scala.annotation.tailrec
@@ -38,7 +38,7 @@ import scala.util.control.{ControlThrowable, NonFatal}
 import com.google.protobuf.{ByteString, GeneratedMessageV3}
 import io.netty.channel.unix.Errors.NativeIoException
 import org.apache.commons.lang3.SystemUtils
-import org.apache.hadoop.fs.Path
+import org.apache.hadoop.fs.{FSDataInputStream, Path}
 import org.roaringbitmap.RoaringBitmap
 
 import org.apache.celeborn.common.CelebornConf
diff --git a/docs/configuration/client.md b/docs/configuration/client.md
index 028650c7f..9c288848c 100644
--- a/docs/configuration/client.md
+++ b/docs/configuration/client.md
@@ -62,6 +62,8 @@ license: |
 | celeborn.client.push.takeTaskMaxWaitAttempts | 1 | Max wait times if no task 
available to push to worker. | 0.3.0 | 
 | celeborn.client.push.takeTaskWaitInterval | 50ms | Wait interval if no task 
available to push to worker. | 0.3.0 | 
 | celeborn.client.push.timeout | 120s | Timeout for a task to push data rpc 
message. This value should better be more than twice of 
`celeborn.<module>.push.timeoutCheck.interval` | 0.3.0 | 
+| celeborn.client.readLocalShuffleFile.enabled | false | Enable read local 
shuffle file for clusters that co-deployed with yarn node manager. | 0.3.1 | 
+| celeborn.client.readLocalShuffleFile.threads | 4 | Threads count for read 
local shuffle file. | 0.3.1 | 
 | celeborn.client.registerShuffle.maxRetries | 3 | Max retry times for client 
to register shuffle. | 0.3.0 | 
 | celeborn.client.registerShuffle.retryWait | 3s | Wait time before next retry 
if register shuffle failed. | 0.3.0 | 
 | celeborn.client.requestCommitFiles.maxRetries | 4 | Max retry times for 
requestCommitFiles RPC. | 0.3.0 | 
diff --git 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/FileWriter.java
 
b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/FileWriter.java
index 7054ad108..e1a8ef8a8 100644
--- 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/FileWriter.java
+++ 
b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/FileWriter.java
@@ -41,6 +41,7 @@ import org.apache.celeborn.common.protocol.PartitionSplitMode;
 import org.apache.celeborn.common.protocol.PartitionType;
 import org.apache.celeborn.common.protocol.StorageInfo;
 import org.apache.celeborn.common.unsafe.Platform;
+import org.apache.celeborn.common.util.FileChannelUtils;
 import org.apache.celeborn.service.deploy.worker.WorkerSource;
 import 
org.apache.celeborn.service.deploy.worker.congestcontrol.CongestionController;
 import org.apache.celeborn.service.deploy.worker.memory.MemoryManager;
@@ -282,9 +283,7 @@ public abstract class FileWriter implements DeviceObserver {
         if (channel != null) {
           channel.close();
         }
-        if (fileInfo.isHdfs()) {
-          streamClose.run();
-        }
+        streamClose.run();
       } catch (IOException e) {
         logger.warn("close file writer {} failed", this, e);
       }
diff --git 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapDataPartition.java
 
b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapDataPartition.java
index e02e2a362..c423aacb0 100644
--- 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapDataPartition.java
+++ 
b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapDataPartition.java
@@ -37,6 +37,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.meta.FileInfo;
+import org.apache.celeborn.common.util.FileChannelUtils;
 import org.apache.celeborn.common.util.JavaUtils;
 import org.apache.celeborn.service.deploy.worker.memory.BufferQueue;
 import org.apache.celeborn.service.deploy.worker.memory.BufferRecycler;
diff --git 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionFileWriter.java
 
b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionFileWriter.java
index 848b5f32b..98ba4a13b 100644
--- 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionFileWriter.java
+++ 
b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/MapPartitionFileWriter.java
@@ -34,6 +34,7 @@ import org.apache.celeborn.common.meta.FileInfo;
 import org.apache.celeborn.common.metrics.source.AbstractSource;
 import org.apache.celeborn.common.protocol.PartitionSplitMode;
 import org.apache.celeborn.common.protocol.PartitionType;
+import org.apache.celeborn.common.util.FileChannelUtils;
 import org.apache.celeborn.common.util.Utils;
 
 /*
@@ -130,11 +131,13 @@ public final class MapPartitionFileWriter extends 
FileWriter {
           flushIndex();
         },
         () -> {
-          if 
(StorageManager.hadoopFs().exists(fileInfo.getHdfsPeerWriterSuccessPath())) {
-            StorageManager.hadoopFs().delete(fileInfo.getHdfsPath(), false);
-            deleted = true;
-          } else {
-            
StorageManager.hadoopFs().create(fileInfo.getHdfsWriterSuccessPath()).close();
+          if (fileInfo.isHdfs()) {
+            if 
(StorageManager.hadoopFs().exists(fileInfo.getHdfsPeerWriterSuccessPath())) {
+              StorageManager.hadoopFs().delete(fileInfo.getHdfsPath(), false);
+              deleted = true;
+            } else {
+              
StorageManager.hadoopFs().create(fileInfo.getHdfsWriterSuccessPath()).close();
+            }
           }
         },
         () -> {
diff --git 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionFileWriter.java
 
b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionFileWriter.java
index 06db73b0a..d9b89849b 100644
--- 
a/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionFileWriter.java
+++ 
b/worker/src/main/java/org/apache/celeborn/service/deploy/worker/storage/ReducePartitionFileWriter.java
@@ -95,18 +95,20 @@ public final class ReducePartitionFileWriter extends 
FileWriter {
           }
         },
         () -> {
-          if 
(StorageManager.hadoopFs().exists(fileInfo.getHdfsPeerWriterSuccessPath())) {
-            StorageManager.hadoopFs().delete(fileInfo.getHdfsPath(), false);
-            deleted = true;
-          } else {
-            
StorageManager.hadoopFs().create(fileInfo.getHdfsWriterSuccessPath()).close();
-            FSDataOutputStream indexOutputStream =
-                StorageManager.hadoopFs().create(fileInfo.getHdfsIndexPath());
-            indexOutputStream.writeInt(fileInfo.getChunkOffsets().size());
-            for (Long offset : fileInfo.getChunkOffsets()) {
-              indexOutputStream.writeLong(offset);
+          if (fileInfo.isHdfs()) {
+            if 
(StorageManager.hadoopFs().exists(fileInfo.getHdfsPeerWriterSuccessPath())) {
+              StorageManager.hadoopFs().delete(fileInfo.getHdfsPath(), false);
+              deleted = true;
+            } else {
+              
StorageManager.hadoopFs().create(fileInfo.getHdfsWriterSuccessPath()).close();
+              FSDataOutputStream indexOutputStream =
+                  
StorageManager.hadoopFs().create(fileInfo.getHdfsIndexPath());
+              indexOutputStream.writeInt(fileInfo.getChunkOffsets().size());
+              for (Long offset : fileInfo.getChunkOffsets()) {
+                indexOutputStream.writeLong(offset);
+              }
+              indexOutputStream.close();
             }
-            indexOutputStream.close();
           }
         },
         () -> {});
diff --git 
a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/FetchHandler.scala
 
b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/FetchHandler.scala
index 624c44655..019c8871e 100644
--- 
a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/FetchHandler.scala
+++ 
b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/FetchHandler.scala
@@ -17,12 +17,14 @@
 
 package org.apache.celeborn.service.deploy.worker
 
-import java.{lang, util}
 import java.io.{FileNotFoundException, IOException}
 import java.nio.charset.StandardCharsets
+import java.util
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.function.Consumer
 
+import scala.collection.JavaConverters.asScalaBufferConverter
+
 import com.google.common.base.Throwables
 import io.netty.util.concurrent.{Future, GenericFutureListener}
 
@@ -100,13 +102,14 @@ class FetchHandler(val conf: CelebornConf, val 
transportConf: TransportConf)
         try {
           val pbMsg = TransportMessage.fromByteBuffer(r.body().nioByteBuffer())
           val pbOpenStream = pbMsg.getParsedPayload[PbOpenStream]
-          val (shuffleKey, fileName, startIndex, endIndex, initialCredit) =
+          val (shuffleKey, fileName, startIndex, endIndex, initialCredit, 
readLocalShuffle) =
             (
               pbOpenStream.getShuffleKey,
               pbOpenStream.getFileName,
               pbOpenStream.getStartIndex,
               pbOpenStream.getEndIndex,
-              pbOpenStream.getInitialCredit)
+              pbOpenStream.getInitialCredit,
+              pbOpenStream.getReadLocalShuffle)
           streamShuffleKey = shuffleKey
           streamFileName = fileName
           workerSource.startTimer(WorkerSource.OPEN_STREAM_TIME, 
streamShuffleKey)
@@ -118,7 +121,8 @@ class FetchHandler(val conf: CelebornConf, val 
transportConf: TransportConf)
             endIndex,
             initialCredit,
             r,
-            false)
+            false,
+            readLocalShuffle)
         } catch {
           case _: Exception =>
             // process legacy OpenStream RPCs
@@ -182,7 +186,8 @@ class FetchHandler(val conf: CelebornConf, val 
transportConf: TransportConf)
       endIndex: Int,
       initialCredit: Int,
       request: RpcRequest,
-      isLegacy: Boolean): Unit = {
+      isLegacy: Boolean,
+      readLocalShuffle: Boolean = false): Unit = {
     try {
       var fileInfo = getRawFileInfo(shuffleKey, fileName)
       fileInfo.getPartitionType match {
@@ -198,7 +203,16 @@ class FetchHandler(val conf: CelebornConf, val 
transportConf: TransportConf)
           logDebug(s"Received chunk fetch request $shuffleKey $fileName 
$startIndex " +
             s"$endIndex get file info $fileInfo from client channel " +
             s"${NettyUtils.getRemoteAddress(client.getChannel)}")
-          if (fileInfo.isHdfs) {
+          if (readLocalShuffle) {
+            replyStreamHandler(
+              client,
+              request.requestId,
+              -1,
+              fileInfo.numChunks(),
+              isLegacy,
+              fileInfo.getChunkOffsets,
+              fileInfo.getFilePath)
+          } else if (fileInfo.isHdfs) {
             replyStreamHandler(client, request.requestId, 0, 0, isLegacy)
           } else {
             val buffers = new FileManagedBuffers(fileInfo, transportConf)
@@ -245,18 +259,28 @@ class FetchHandler(val conf: CelebornConf, val 
transportConf: TransportConf)
       requestId: Long,
       streamId: Long,
       numChunks: Int,
-      isLegacy: Boolean): Unit = {
+      isLegacy: Boolean,
+      offsets: util.List[java.lang.Long] = null,
+      filepath: String = ""): Unit = {
     if (isLegacy) {
       client.getChannel.writeAndFlush(new RpcResponse(
         requestId,
         new NioManagedBuffer(new StreamHandle(streamId, 
numChunks).toByteBuffer)))
     } else {
+      val pbStreamHandlerBuilder = 
PbStreamHandler.newBuilder.setStreamId(streamId).setNumChunks(
+        numChunks)
+      if (offsets != null) {
+        pbStreamHandlerBuilder.addAllChunkOffsets(offsets)
+      }
+      if (filepath.nonEmpty) {
+        pbStreamHandlerBuilder.setFullPath(filepath)
+      }
+      val pbStreamHandler = pbStreamHandlerBuilder.build()
       client.getChannel.writeAndFlush(new RpcResponse(
         requestId,
         new NioManagedBuffer(new TransportMessage(
           MessageType.STREAM_HANDLER,
-          PbStreamHandler.newBuilder.setStreamId(streamId).setNumChunks(
-            numChunks).build.toByteArray).toByteBuffer)))
+          pbStreamHandler.toByteArray).toByteBuffer)))
     }
   }
 
diff --git 
a/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ClusterReadWriteTestWithLZ4.scala
 
b/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ClusterReadWriteTestWithLZ4.scala
index cfc14b1d2..bc142e1cc 100644
--- 
a/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ClusterReadWriteTestWithLZ4.scala
+++ 
b/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ClusterReadWriteTestWithLZ4.scala
@@ -25,4 +25,8 @@ class ClusterReadWriteTestWithLZ4 extends ReadWriteTestBase {
     testReadWriteByCode(CompressionCodec.LZ4)
   }
 
+  test(s"test MiniCluster With LZ4 and local read for spark") {
+    testReadWriteByCode(CompressionCodec.LZ4, true)
+  }
+
 }
diff --git 
a/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ClusterReadWriteTestWithZSTD.scala
 
b/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ClusterReadWriteTestWithZSTD.scala
index 8d207cb25..e2fe77928 100644
--- 
a/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ClusterReadWriteTestWithZSTD.scala
+++ 
b/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ClusterReadWriteTestWithZSTD.scala
@@ -25,4 +25,8 @@ class ClusterReadWriteTestWithZSTD extends ReadWriteTestBase {
     testReadWriteByCode(CompressionCodec.ZSTD)
   }
 
+  test(s"test MiniCluster With ZSTD and local read for spark") {
+    testReadWriteByCode(CompressionCodec.ZSTD, true)
+  }
+
 }
diff --git 
a/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ReadWriteTestBase.scala
 
b/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ReadWriteTestBase.scala
index 433f89ea1..e5e3c50c7 100644
--- 
a/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ReadWriteTestBase.scala
+++ 
b/worker/src/test/scala/org/apache/celeborn/service/deploy/cluster/ReadWriteTestBase.scala
@@ -51,7 +51,7 @@ trait ReadWriteTestBase extends AnyFunSuite
     shutdownMiniCluster()
   }
 
-  def testReadWriteByCode(codec: CompressionCodec): Unit = {
+  def testReadWriteByCode(codec: CompressionCodec, readLocalShuffle: Boolean = 
false): Unit = {
     val APP = "app-1"
 
     val clientConf = new CelebornConf()
@@ -59,6 +59,7 @@ trait ReadWriteTestBase extends AnyFunSuite
       .set(CelebornConf.SHUFFLE_COMPRESSION_CODEC.key, codec.name)
       .set(CelebornConf.CLIENT_PUSH_REPLICATE_ENABLED.key, "true")
       .set(CelebornConf.CLIENT_PUSH_BUFFER_MAX_SIZE.key, "256K")
+      .set(CelebornConf.READ_LOCAL_SHUFFLE_FILE, readLocalShuffle)
       .set("celeborn.data.io.numConnectionsPerPeer", "1")
     val lifecycleManager = new LifecycleManager(APP, clientConf)
     val shuffleClient = new ShuffleClientImpl(APP, clientConf, 
UserIdentifier("mock", "mock"))


Reply via email to