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

lollipopjin pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/rocketmq.git


The following commit(s) were added to refs/heads/develop by this push:
     new 50ae7c5093 [ISSUE #10462] Add memory backpressure to dispatch (#10470)
50ae7c5093 is described below

commit 50ae7c50930798aced54c4c24467f847eb684aed
Author: lizhimins <[email protected]>
AuthorDate: Thu Jun 11 14:14:11 2026 +0800

    [ISSUE #10462] Add memory backpressure to dispatch (#10470)
---
 .../rocketmq/tieredstore/MessageStoreConfig.java   | 27 +++++++++++++++
 .../core/MessageStoreDispatcherImpl.java           | 36 +++++++++++--------
 .../core/MessageStoreDispatcherImplTest.java       | 40 +++++++++++++++++++++-
 3 files changed, 88 insertions(+), 15 deletions(-)

diff --git 
a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/MessageStoreConfig.java
 
b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/MessageStoreConfig.java
index d22ab80dd8..fcb2e884bf 100644
--- 
a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/MessageStoreConfig.java
+++ 
b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/MessageStoreConfig.java
@@ -122,6 +122,17 @@ public class MessageStoreConfig {
     private double readAheadCacheSizeThresholdRate = 0.3;
 
     private int tieredStoreMaxPendingLimit = 10000;
+    /**
+     * Minimum fraction of max heap that must remain free for dispatch to 
proceed.
+     * Actual threshold = min(ratio × maxMemory, maxBytes).
+     * Default 0.1 (10%).
+     */
+    private double tieredStoreDispatchMinFreeMemoryRatio = 0.1;
+    /**
+     * Upper cap on the backpressure threshold in bytes, preventing the 
ratio-based
+     * threshold from becoming too large on big-heap instances. Default 1GB.
+     */
+    private long tieredStoreDispatchMinFreeMemoryMaxBytes = 1024 * 1024 * 
1024L;
     private boolean tieredStoreCrcCheckEnable = false;
 
     private String tieredStoreFilePath = "";
@@ -372,6 +383,22 @@ public class MessageStoreConfig {
         this.tieredStoreMaxPendingLimit = tieredStoreMaxPendingLimit;
     }
 
+    public double getTieredStoreDispatchMinFreeMemoryRatio() {
+        return tieredStoreDispatchMinFreeMemoryRatio;
+    }
+
+    public void setTieredStoreDispatchMinFreeMemoryRatio(double 
tieredStoreDispatchMinFreeMemoryRatio) {
+        this.tieredStoreDispatchMinFreeMemoryRatio = 
tieredStoreDispatchMinFreeMemoryRatio;
+    }
+
+    public long getTieredStoreDispatchMinFreeMemoryMaxBytes() {
+        return tieredStoreDispatchMinFreeMemoryMaxBytes;
+    }
+
+    public void setTieredStoreDispatchMinFreeMemoryMaxBytes(long 
tieredStoreDispatchMinFreeMemoryMaxBytes) {
+        this.tieredStoreDispatchMinFreeMemoryMaxBytes = 
tieredStoreDispatchMinFreeMemoryMaxBytes;
+    }
+
     public boolean isTieredStoreCrcCheckEnable() {
         return tieredStoreCrcCheckEnable;
     }
diff --git 
a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImpl.java
 
b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImpl.java
index bcc4e225da..52e1c82a1d 100644
--- 
a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImpl.java
+++ 
b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImpl.java
@@ -30,7 +30,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.rocketmq.common.ServiceThread;
@@ -69,7 +68,6 @@ public class MessageStoreDispatcherImpl extends ServiceThread 
implements Message
     protected final FlatFileStore flatFileStore;
     protected final MessageStoreExecutor storeExecutor;
     protected final MessageStoreFilter topicFilter;
-    protected final Semaphore semaphore;
     protected final IndexService indexService;
     protected final Map<FlatFileInterface, GroupCommitContext> 
failedGroupCommitMap;
 
@@ -78,8 +76,6 @@ public class MessageStoreDispatcherImpl extends ServiceThread 
implements Message
         this.storeConfig = messageStore.getStoreConfig();
         this.defaultStore = messageStore.getDefaultStore();
         this.brokerName = storeConfig.getBrokerName();
-        this.semaphore = new Semaphore(
-            this.storeConfig.getTieredStoreMaxPendingLimit() / 4);
         this.topicFilter = messageStore.getTopicFilter();
         this.flatFileStore = messageStore.getFlatFileStore();
         this.storeExecutor = messageStore.getStoreExecutor();
@@ -97,16 +93,28 @@ public class MessageStoreDispatcherImpl extends 
ServiceThread implements Message
         return failedGroupCommitMap;
     }
 
-    public void dispatchWithSemaphore(FlatFileInterface flatFile) {
+    protected boolean isMemoryEnough(FlatFileInterface flatFile) {
+        Runtime runtime = Runtime.getRuntime();
+        long availableMemory = runtime.maxMemory() - runtime.totalMemory() + 
runtime.freeMemory();
+        long ratioThreshold = (long) (runtime.maxMemory() * 
storeConfig.getTieredStoreDispatchMinFreeMemoryRatio());
+        long memoryThreshold = Math.min(ratioThreshold, 
storeConfig.getTieredStoreDispatchMinFreeMemoryMaxBytes());
+        if (availableMemory < memoryThreshold) {
+            log.warn("MessageStore dispatch skipped due to memory 
backpressure, " +
+                "availableMemory={}MB, threshold={}MB, topic={}, queueId={}",
+                availableMemory / (1024 * 1024), memoryThreshold / (1024 * 
1024),
+                flatFile.getMessageQueue().getTopic(), 
flatFile.getMessageQueue().getQueueId());
+            return false;
+        }
+        return true;
+    }
+
+    public void dispatch(FlatFileInterface flatFile) {
+        if (stopped || !isMemoryEnough(flatFile)) {
+            return;
+        }
         try {
-            if (stopped) {
-                return;
-            }
-            semaphore.acquire();
-            this.doScheduleDispatch(flatFile, false)
-                .whenComplete((future, throwable) -> semaphore.release());
+            this.doScheduleDispatch(flatFile, false);
         } catch (Throwable t) {
-            semaphore.release();
             log.error("MessageStore dispatch error, topic={}, queueId={}",
                 flatFile.getMessageQueue().getTopic(), 
flatFile.getMessageQueue().getQueueId(), t);
         }
@@ -313,7 +321,7 @@ public class MessageStoreDispatcherImpl extends 
ServiceThread implements Message
                             }
                         }
                         if (success && repeat) {
-                            storeExecutor.commonExecutor.submit(() -> 
dispatchWithSemaphore(flatFile));
+                            storeExecutor.commonExecutor.submit(() -> 
dispatch(flatFile));
                         }
                     }
                 );
@@ -378,7 +386,7 @@ public class MessageStoreDispatcherImpl extends 
ServiceThread implements Message
         log.info("{} service started", this.getServiceName());
         while (!this.isStopped()) {
             try {
-                
flatFileStore.deepCopyFlatFileToList().forEach(this::dispatchWithSemaphore);
+                flatFileStore.deepCopyFlatFileToList().forEach(this::dispatch);
 
                 releaseClosedPendingGroupCommit();
 
diff --git 
a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImplTest.java
 
b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImplTest.java
index 7a43e1ede8..2a0b0ad418 100644
--- 
a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImplTest.java
+++ 
b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImplTest.java
@@ -37,6 +37,7 @@ import org.apache.rocketmq.tieredstore.MessageStoreExecutor;
 import org.apache.rocketmq.tieredstore.TieredMessageStore;
 import org.apache.rocketmq.tieredstore.common.GroupCommitContext;
 import org.apache.rocketmq.tieredstore.file.FlatFileFactory;
+import org.apache.rocketmq.tieredstore.file.FlatFileInterface;
 import org.apache.rocketmq.tieredstore.file.FlatFileStore;
 import org.apache.rocketmq.tieredstore.file.FlatMessageFile;
 import org.apache.rocketmq.tieredstore.index.IndexItem;
@@ -311,9 +312,46 @@ public class MessageStoreDispatcherImplTest {
         Mockito.doAnswer(mock -> {
             result.set(true);
             return true;
-        }).when(dispatcherSpy).dispatchWithSemaphore(any());
+        }).when(dispatcherSpy).dispatch(any(FlatFileInterface.class));
         dispatcherSpy.start();
         Awaitility.await().atMost(Duration.ofSeconds(10)).until(result::get);
         dispatcherSpy.shutdown();
     }
+
+    @Test
+    public void isMemoryEnoughTest() {
+        MessageStore defaultStore = Mockito.mock(MessageStore.class);
+        messageStore = Mockito.mock(TieredMessageStore.class);
+        IndexService indexService =
+            new IndexStoreService(new FlatFileFactory(metadataStore, 
storeConfig), storePath);
+        Mockito.when(messageStore.getDefaultStore()).thenReturn(defaultStore);
+        Mockito.when(messageStore.getStoreConfig()).thenReturn(storeConfig);
+        Mockito.when(messageStore.getStoreExecutor()).thenReturn(executor);
+        Mockito.when(messageStore.getFlatFileStore()).thenReturn(fileStore);
+        Mockito.when(messageStore.getIndexService()).thenReturn(indexService);
+        MessageStoreDispatcherImpl dispatcher = new 
MessageStoreDispatcherImpl(messageStore);
+
+        FlatFileInterface flatFile = Mockito.mock(FlatFileInterface.class);
+        Mockito.when(flatFile.getMessageQueue()).thenReturn(mq);
+
+        // (1) memory sufficient: threshold = min(maxMemory * 0, 0) = 0 → 
always true
+        storeConfig.setTieredStoreDispatchMinFreeMemoryRatio(0.0f);
+        storeConfig.setTieredStoreDispatchMinFreeMemoryMaxBytes(0L);
+        Assert.assertTrue(dispatcher.isMemoryEnough(flatFile));
+
+        // (2) memory below threshold: threshold = min(maxMemory * 1.0, 
MAX_VALUE) = maxMemory → always false
+        storeConfig.setTieredStoreDispatchMinFreeMemoryRatio(1.0f);
+        
storeConfig.setTieredStoreDispatchMinFreeMemoryMaxBytes(Long.MAX_VALUE);
+        Assert.assertFalse(dispatcher.isMemoryEnough(flatFile));
+
+        // (3) maxBytes cap: ratio = 1.0 → ratioThreshold = maxMemory
+        // maxBytes = 1 → threshold = min(maxMemory, 1) = 1 → available > 1 → 
true
+        storeConfig.setTieredStoreDispatchMinFreeMemoryMaxBytes(1L);
+        Assert.assertTrue(dispatcher.isMemoryEnough(flatFile));
+        // maxBytes = MAX_VALUE → threshold = maxMemory → available < 
maxMemory → false
+        
storeConfig.setTieredStoreDispatchMinFreeMemoryMaxBytes(Long.MAX_VALUE);
+        Assert.assertFalse(dispatcher.isMemoryEnough(flatFile));
+
+        dispatcher.shutdown();
+    }
 }

Reply via email to