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

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


The following commit(s) were added to refs/heads/main by this push:
     new 89fc9bd549 [CELEBORN-2516] Worker decommission does not wait for 
shuffles held only in memory files
89fc9bd549 is described below

commit 89fc9bd5491e93552c6903f48582273545c2d7c9
Author: strelok89 <[email protected]>
AuthorDate: Fri Oct 30 23:17:02 2026 +0800

    [CELEBORN-2516] Worker decommission does not wait for shuffles held only in 
memory files
    
    ### What changes were proposed in this pull request?
    `StorageManager.shuffleKeySet()` now includes `memoryFileInfos.keySet()` 
along with `diskFileInfos.keySet()`.
    
    **Chart:** also keeps a draining worker resolvable. A terminating pod is 
dropped from its headless service immediately, so executors fetching from a 
draining worker hit `UnknownHostException` about a second into the drain, even 
with the wait fixed. With `worker.drain.enabled` the worker service now sets 
`publishNotReadyAddresses: true`. Note: #3854 sets this flag unconditionally on 
the worker service; if it merges first, this hunk becomes redundant.
    
    ### Why are the changes needed?
    With the MEMORY storage tier enabled 
(`celeborn.storage.availableTypes=MEMORY,SSD`), a shuffle whose partitions are 
still in memory files is invisible to `shuffleKeySet()`. 
`Worker.decommissionWorker()` waits until that set is empty, so a 
decommissioning worker that holds only memory-file shuffles exits immediately 
(`Waiting for all shuffle expired cost 0ms`) with slots still active. Clients 
reading those shuffles then fail with fetch failures and resubmit stages. We 
hit this on 0.7.0 [...]
    
    Every other caller also wants "shuffles this worker holds", whatever the 
storage tier:
    - the graceful-shutdown check in `WorkerStatusManager`
    - the `UnreleasedShuffleCount` gauge
    - the active shuffle keys reported in the heartbeat
    - the shuffle list on the HTTP endpoint
    - the expired-shuffle cleanup on shutdown, which already handles memory 
keys in `cleanupExpiredShuffleKey`
    
    ### Does this PR resolve a correctness bug?
    - [ ] Yes
    
    ### Does this PR introduce _any_ user-facing change?
    - [x] Yes. With the MEMORY tier enabled, decommission and graceful shutdown 
now wait for memory-file shuffles, the `UnreleasedShuffleCount` gauge counts 
them, and `/api/v1/shuffles` lists them.
    
    ### How was this patch tested?
    New `StorageManagerSuite` case: a shuffle held only in a memory file is 
returned by `shuffleKeySet()`. Existing `StorageManagerSuite` passes. Plus 
helm-unittest: two new worker-service cases (absent by default, set when the 
drain is enabled); 136/136 pass.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Closes #3916 from strelok89/fix-decommission-memory-shuffle.
    
    Authored-by: strelok89 <[email protected]>
    Signed-off-by: Nicholas Jiang <[email protected]>
---
 charts/celeborn/README.md                                |  5 +++++
 charts/celeborn/templates/worker/service.yaml            |  4 ++++
 charts/celeborn/tests/worker/service_test.yaml           | 15 +++++++++++++++
 .../service/deploy/worker/storage/StorageManager.scala   |  2 ++
 .../deploy/worker/storage/StorageManagerSuite.scala      | 16 +++++++++++++++-
 5 files changed, 41 insertions(+), 1 deletion(-)

diff --git a/charts/celeborn/README.md b/charts/celeborn/README.md
index c679f5c364..c2886828ce 100644
--- a/charts/celeborn/README.md
+++ b/charts/celeborn/README.md
@@ -171,6 +171,11 @@ drain, so the chart refuses to render `worker.drain` 
together with either settin
 `celeborn`. It can only see what is set there: enabling TLS or authentication 
some other way,
 such as an extra config file, still bypasses the drain.
 
+A terminating pod is dropped from its headless service straight away, so a 
draining worker
+would stop resolving the moment the drain starts, and clients still fetching 
from it would fail
+with `UnknownHostException`. With the drain enabled the worker service 
therefore sets
+`publishNotReadyAddresses: true`, keeping the worker's DNS record until the 
pod is gone.
+
 It is off by default. Turn it on alongside `worker.autoscaling`, or leave it 
off and treat
 scale-in as destructive, only letting it happen when the fleet is idle.
 
diff --git a/charts/celeborn/templates/worker/service.yaml 
b/charts/celeborn/templates/worker/service.yaml
index 90b5940275..c4fb93f618 100644
--- a/charts/celeborn/templates/worker/service.yaml
+++ b/charts/celeborn/templates/worker/service.yaml
@@ -29,4 +29,8 @@ spec:
     {{- include "celeborn.worker.selectorLabels" . | nindent 4 }}
   type: {{ .Values.service.type }}
   clusterIP: None
+  {{- if .Values.worker.drain.enabled }}
+  # Keep a draining worker resolvable until it exits. See README.md.
+  publishNotReadyAddresses: true
+  {{- end }}
   {{- include "celeborn.worker.service.port" . | nindent 2 }}
diff --git a/charts/celeborn/tests/worker/service_test.yaml 
b/charts/celeborn/tests/worker/service_test.yaml
index 27a50e9949..291f5cce86 100644
--- a/charts/celeborn/tests/worker/service_test.yaml
+++ b/charts/celeborn/tests/worker/service_test.yaml
@@ -47,3 +47,18 @@ tests:
       - equal:
           path: spec.type
           value: NodePort
+
+  - it: Should not publish not-ready addresses by default
+    asserts:
+      - notExists:
+          path: spec.publishNotReadyAddresses
+
+  - it: Should publish not-ready addresses when the worker drain is enabled
+    set:
+      worker:
+        drain:
+          enabled: true
+    asserts:
+      - equal:
+          path: spec.publishNotReadyAddresses
+          value: true
diff --git 
a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
 
b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
index 80aee90a77..50f2b5f888 100644
--- 
a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
+++ 
b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
@@ -637,6 +637,8 @@ final private[worker] class StorageManager(conf: 
CelebornConf, workerSource: Abs
   def shuffleKeySet(): util.HashSet[String] = {
     val hashSet = new util.HashSet[String]()
     hashSet.addAll(diskFileInfos.keySet())
+    // A shuffle can live only in memory files; decommission and graceful 
shutdown wait on it too.
+    hashSet.addAll(memoryFileInfos.keySet())
     hashSet
   }
 
diff --git 
a/worker/src/test/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManagerSuite.scala
 
b/worker/src/test/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManagerSuite.scala
index 57505e115c..c6a1e72024 100644
--- 
a/worker/src/test/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManagerSuite.scala
+++ 
b/worker/src/test/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManagerSuite.scala
@@ -36,7 +36,7 @@ import org.apache.celeborn.CelebornFunSuite
 import org.apache.celeborn.common.CelebornConf
 import org.apache.celeborn.common.CelebornConf.{WORKER_DISK_RESERVE_SIZE, 
WORKER_GRACEFUL_SHUTDOWN_ENABLED, WORKER_GRACEFUL_SHUTDOWN_RECOVER_PATH, 
WORKER_GRACEFUL_SHUTDOWN_SAVE_COMMITTED_FILEINFO_INTERVAL, WORKER_STORAGE_DIRS}
 import org.apache.celeborn.common.identity.UserIdentifier
-import org.apache.celeborn.common.meta.{DiskFileInfo, DiskInfo, DiskStatus}
+import org.apache.celeborn.common.meta.{DiskFileInfo, DiskInfo, DiskStatus, 
MemoryFileInfo, ReduceFileMeta}
 import org.apache.celeborn.common.protocol.{PartitionLocation, 
PartitionSplitMode, PartitionType, StorageInfo}
 import org.apache.celeborn.common.util.{CelebornExitKind, JavaUtils, Utils}
 import org.apache.celeborn.service.deploy.worker.WorkerSource
@@ -57,6 +57,20 @@ class StorageManagerSuite extends CelebornFunSuite with 
MockitoHelper {
     storageManager.saveAllCommittedFileInfosToDB()
   }
 
+  test("shuffleKeySet includes shuffles held only in memory files") {
+    val conf = new CelebornConf()
+    val storageManager = new StorageManager(conf, new WorkerSource(conf))
+    val fileInfo =
+      new MemoryFileInfo(
+        UserIdentifier("tenant", "user"),
+        false,
+        new ReduceFileMeta(8 * 1024 * 1024))
+    storageManager.memoryFileInfos
+      .computeIfAbsent("app-1-0", _ => JavaUtils.newConcurrentHashMap[String, 
MemoryFileInfo]())
+      .put("0-0-0", fileInfo)
+    assert(storageManager.shuffleKeySet().asScala == Set("app-1-0"))
+  }
+
   test("[CELEBORN-2495] committedFileInfos should not leak when graceful 
shutdown is disabled") {
     withDiskFile(gracefulShutdown = false) { (storageManager, fileInfo, _) =>
       storageManager.notifyFileInfoCommitted("app-1-0", "0-0-0", fileInfo)

Reply via email to