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

mjsax pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git


The following commit(s) were added to refs/heads/trunk by this push:
     new a1d9d8a5175 KAFKA-20829: Throttle StreamThread shutdown rebalance 
enforcement to avoid log flood (#22917)
a1d9d8a5175 is described below

commit a1d9d8a5175600ec01b9fd07cef038c63c6dcc47
Author: Adam Souquières <[email protected]>
AuthorDate: Sat Jul 25 05:07:47 2026 +0200

    KAFKA-20829: Throttle StreamThread shutdown rebalance enforcement to avoid 
log flood (#22917)
    
    When a Kafka Streams application requests an app-wide shutdown, every
    StreamThread on the classic rebalance protocol floods the log with:
    `Request joining group due to: Shutdown requested`
    
    This PR gates the `enforceRebalance` call behind the existing 10s
    throttle that already guards  WARN logs, so both fire together at most
    once per 10s.
    
    Reviewers: Matthias J. Sax <[email protected]>
---
 .../streams/processor/internals/StreamThread.java  | 20 ++++-----
 .../processor/internals/StreamThreadTest.java      | 52 ++++++++++++++++++++++
 2 files changed, 60 insertions(+), 12 deletions(-)

diff --git 
a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java
 
b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java
index dcd853e2a88..8a74ae6df24 100644
--- 
a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java
+++ 
b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java
@@ -1115,20 +1115,16 @@ public class StreamThread extends Thread implements 
ProcessingThread {
         if (assignmentErrorCode.get() == 
AssignorError.SHUTDOWN_REQUESTED.code()) {
             final long now = time.milliseconds();
             final long lastLogged = lastShutdownWarningTimestamp.get();
-            if (now - lastLogged >= 10_000L) {
-                if (lastShutdownWarningTimestamp.compareAndSet(lastLogged, 
now)) {
-                    log.warn("Detected that shutdown was requested. " +
-                            "All clients in this app will now begin to 
shutdown");
+
+            if (now - lastLogged >= 10_000L && 
lastShutdownWarningTimestamp.compareAndSet(lastLogged, now)) {
+                log.warn("Detected that shutdown was requested. " +
+                        "All clients in this app will now begin to shutdown");
+                // The classic protocol propagates the shutdown request via an 
enforced rebalance,
+                // whereas the Streams protocol (KIP-1071) uses the group 
heartbeat.
+                if (streamsRebalanceData.isEmpty()) {
+                    mainConsumer.enforceRebalance("Shutdown requested");
                 }
             }
-            // Under the classic protocol the shutdown request is propagated 
to the rest of the group
-            // by the assignor during a rebalance, so we need to enforce one. 
Under the Streams group
-            // protocol (KIP-1071) the request is propagated through the group 
heartbeat (see
-            // sendShutdownRequest), and enforceRebalance is not supported by 
the consumer (it would
-            // only log a warning), so we skip it.
-            if (streamsRebalanceData.isEmpty()) {
-                mainConsumer.enforceRebalance("Shutdown requested");
-            }
         }
     }
 
diff --git 
a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamThreadTest.java
 
b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamThreadTest.java
index dd37e70ef02..13d4d3631da 100644
--- 
a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamThreadTest.java
+++ 
b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamThreadTest.java
@@ -4095,6 +4095,58 @@ public class StreamThreadTest {
         verify(mainConsumer).enforceRebalance("Shutdown requested");
     }
 
+    @Test
+    public void 
shouldThrottleEnforceRebalanceOnRepeatedShutdownRequestsUnderClassicProtocol() {
+        final MockTime shutdownTime = new MockTime(0, 100_000L, 0L);
+        final ConsumerGroupMetadata consumerGroupMetadata = 
Mockito.mock(ConsumerGroupMetadata.class);
+        
when(consumerGroupMetadata.groupInstanceId()).thenReturn(Optional.empty());
+        when(mainConsumer.groupMetadata()).thenReturn(consumerGroupMetadata);
+        final Properties props = configProps(false, false);
+        final StreamsMetadataState streamsMetadataState = new 
StreamsMetadataState(
+            new TopologyMetadata(internalTopologyBuilder, new 
StreamsConfig(props)),
+            StreamsMetadataState.UNKNOWN_HOST,
+            new LogContext(String.format("stream-client [%s] ", CLIENT_ID))
+        );
+        final StreamsConfig config = new StreamsConfig(props);
+        thread = new StreamThread(
+            shutdownTime,
+            config,
+            null,
+            mainConsumer,
+            consumer,
+            changelogReader,
+            null,
+            mock(TaskManager.class),
+            null,
+            new StreamsMetricsImpl(metrics, CLIENT_ID, mockTime),
+            new TopologyMetadata(internalTopologyBuilder, config),
+            PROCESS_ID,
+            CLIENT_ID,
+            new LogContext(""),
+            new AtomicInteger(),
+            new AtomicLong(Long.MAX_VALUE),
+            new LinkedList<>(),
+            mock(Runnable.class),
+            HANDLER,
+            null,
+            Optional.empty(),
+            streamsMetadataState,
+            null,
+            -1L
+        ).updateThreadMetadata(adminClientId(CLIENT_ID));
+
+        thread.sendShutdownRequest();
+
+        for (int i = 0; i < 1_000; i++) {
+            thread.maybeSendShutdown();
+        }
+        verify(mainConsumer, times(1)).enforceRebalance("Shutdown requested");
+
+        shutdownTime.sleep(10_000L);
+        thread.maybeSendShutdown();
+        verify(mainConsumer, times(2)).enforceRebalance("Shutdown requested");
+    }
+
     @Test
     public void 
testStreamsProtocolRunOnceWithoutProcessingThreadsMissingSourceTopic() {
         final ConsumerGroupMetadata consumerGroupMetadata = 
Mockito.mock(ConsumerGroupMetadata.class);

Reply via email to