frankvicky commented on code in PR #22458:
URL: https://github.com/apache/kafka/pull/22458#discussion_r4360732067


##########
streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java:
##########
@@ -1540,6 +1581,22 @@ private long pollPhase() {
             taskManager.updateNextOffsets(records.nextOffsets());
         }
 
+        // soft cap — read isn't atomic across tasks, so small overshoots are 
fine.
+        final long bufferSize = taskManager.getInputBufferSizeInBytes();
+        if (maxBufferSizeInBytes.get() != UNDEFINED_INPUT_BUFFER_MAX_BYTES && 
bufferSize > maxBufferSizeInBytes.get()) {
+            // pause only non-empty partitions; pausing empty ones risks 
ordering deadlock (KAFKA-13152).
+            // copy first — nonEmptyPartitions may be immutable, and pausing 
an unassigned partition
+            // throws, so intersect with assignment (mirrors resume side).
+            final Set<TopicPartition> nonEmptyPartitions = new 
HashSet<>(taskManager.nonEmptyPartitions());
+            nonEmptyPartitions.retainAll(mainConsumer.assignment());
+            if (!nonEmptyPartitions.isEmpty()) {
+                log.info("Buffered records size {} bytes exceeds {}. Pausing 
partitions {} from the consumer",

Review Comment:
   Pause and resume both log at INFO with the full partition list. Near the cap 
this fires every iteration (pause at `> cap`, resume at `<= cap`, no 
hysteresis), so it becomes two INFO lines per loop. Please move to DEBUG, same 
for the resume log above.



##########
streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java:
##########
@@ -1215,8 +1226,32 @@ private void subscribeConsumer() {
         }
     }
 
-    public void resizeCache(final long size) {
-        cacheResizeSize.set(size);
+    public void resizeCacheAndBufferMemory(final long cacheSize, final long 
maxBufferSize) {
+        cacheResizeSize.set(cacheSize);
+        maxBufferSizeInBytes.set(maxBufferSize);
+    }
+
+    // Drop revoked partitions so we never later resume one now owned by 
another path (reset/rebalance).
+    void removePartitionsFromBufferOverflowTracking(final 
Collection<TopicPartition> revokedPartitions) {
+        partitionsPausedForBufferOverflow.removeAll(revokedPartitions);
+    }
+
+    private void maybeResumePartitionsPausedForBufferOverflow() {

Review Comment:
   This still resumes partitions owned by another pauser on the 
`closeDirtyAndRevive` path. There the task's partitions stay in 
`mainConsumer.assignment()` but the task is removed from 
`activeTasksPerPartition`, and `closeDirty` clears its buffer so total bytes 
drop below the cap. The next call here resumes them, the following poll returns 
records for them, and `getActiveTask` throws NPE and kills the thread.
   
   Revoke/lost pruning doesn't cover this. I'd suggest also intersecting 
`toResume` with the input partitions of currently active-initialized tasks (a 
small `TaskManager` helper), which closes this and any similar lifecycle gap in 
one place.



##########
streams/src/main/java/org/apache/kafka/streams/internals/StreamsConfigUtils.java:
##########
@@ -85,4 +87,50 @@ public static long totalCacheSize(final StreamsConfig 
config) {
         // only new or no config set. Use default or user specified value.
         return config.getLong(STATESTORE_CACHE_MAX_BYTES_CONFIG);
     }
+
+    // Returns -1 unless the deprecated config is the only one set; -1 
disables the legacy per-partition pause.
+    @SuppressWarnings("deprecation")
+    public static int getBufferedRecordsPerPartition(final StreamsConfig 
config) {
+        if 
(config.originals().containsKey(BUFFERED_RECORDS_PER_PARTITION_CONFIG) && 
config.originals().containsKey(INPUT_BUFFER_MAX_BYTES_CONFIG)) {
+            LOG.warn("Both deprecated config {} and the new config {} are set, 
hence {} is ignored and {} will be used instead to keep memory usage under 
control.",
+                BUFFERED_RECORDS_PER_PARTITION_CONFIG,
+                INPUT_BUFFER_MAX_BYTES_CONFIG,
+                BUFFERED_RECORDS_PER_PARTITION_CONFIG,
+                INPUT_BUFFER_MAX_BYTES_CONFIG);
+            return -1;
+        } else if 
(config.originals().containsKey(BUFFERED_RECORDS_PER_PARTITION_CONFIG)) {
+            LOG.warn("Deprecated config {} is set, and will be used; we 
suggest setting the new config {} to keep memory usage under control " +
+                    "instead as deprecated {} would be removed in the future.",
+                BUFFERED_RECORDS_PER_PARTITION_CONFIG,
+                INPUT_BUFFER_MAX_BYTES_CONFIG,
+                BUFFERED_RECORDS_PER_PARTITION_CONFIG);
+            return config.getInt(BUFFERED_RECORDS_PER_PARTITION_CONFIG);
+        }
+        return -1;
+    }
+
+    // Always returns a usable bytes cap so memory stays bounded (KIP-770). 
Only when the legacy
+    // config alone is set do we fall through to the new config's default; the 
legacy per-partition
+    // record cap still applies in parallel via getBufferedRecordsPerPartition.
+    @SuppressWarnings("deprecation")
+    public static long getInputBufferMaxBytes(final StreamsConfig config) {

Review Comment:
   The three branches return the same value and the "both set" warning is also 
logged by `getBufferedRecordsPerPartition`, so users see it twice. Could be 
reduced to one `if` for the deprecated-only warning.



##########
streams/src/main/java/org/apache/kafka/streams/KafkaStreams.java:
##########
@@ -1456,8 +1467,19 @@ private long maxUncommittedBytesPerThread(final int 
numStreamThreads) {
         return divisor == 0 ? totalMax : totalMax / divisor;
     }
 
-    private void resizeThreadCache(final long cacheSizePerThread) {
-        processStreamThread(thread -> thread.resizeCache(cacheSizePerThread));
+    // Pass through -1L unchanged so StreamThread can tell the bytes guard is 
disabled.
+    private long bufferSizePerThread(final int numStreamThreads) {
+        if (inputBufferMaxBytes == -1L) {

Review Comment:
   `getInputBufferMaxBytes` always returns the config value (`atLeast(0)`), so 
this `-1` branch is unreachable and the comment is stale. Either drop it or 
keep a real way to disable the guard.



##########
docs/streams/upgrade-guide.md:
##########
@@ -97,6 +97,8 @@ As part of 
[KIP-1284](https://cwiki.apache.org/confluence/spaces/KAFKA/pages/406
 
 
[KIP-1356](https://cwiki.apache.org/confluence/spaces/KAFKA/pages/430408653/KIP-1356+Introduce+IQv2+for+headers-aware+state+stores)
 adds four `@Evolving` IQv2 query types that return record headers from the 
[header-aware state stores](#kip-1271-headers-aware-stores) introduced by 
KIP-1271: `TimestampedKeyWithHeadersQuery`, `TimestampedRangeWithHeadersQuery`, 
`TimestampedWindowKeyWithHeadersQuery`, and 
`TimestampedWindowRangeWithHeadersQuery`. Their results carry headers as a 
[`ReadOnlyRecord`](/{version}/javadoc/org/apache/kafka/streams/processor/api/ReadOnlyRecord.html)
 — or, for the range and window forms, a closeable 
[`ReadOnlyRecordIterator`](/{version}/javadoc/org/apache/kafka/streams/state/ReadOnlyRecordIterator.html).
 See the [interactive queries 
guide](/{version}/streams/developer-guide/interactive-queries/#header-aware-stores-interactive-queries)
 for usage and behavior details.
 
+[KIP-770](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=186878390)
 is now fully implemented: config `input.buffer.max.bytes` (LONG, default 512 
MB, topology-wide) is introduced and `buffered.records.per.partition` is 
deprecated ([KAFKA-13152](https://issues.apache.org/jira/browse/KAFKA-13152)). 
The total buffer is split evenly across stream threads (`input.buffer.max.bytes 
/ num.stream.threads`); when a thread's tasks collectively exceed their slice, 
the main consumer pauses every non-empty input partition on that thread and 
resumes once the buffered bytes drop back below the cap. When 
`input.buffer.max.bytes` is set, the new bytes-based topology-wide pause 
supersedes the legacy per-partition record-count pause. If only the deprecated 
`buffered.records.per.partition` is set, the bytes cap is still applied at its 
default (512 MB) so memory stays bounded, while the legacy per-partition record 
cap continues to apply in parallel; a deprecation warning is logged. Memor
 y note: upgraders who never set `input.buffer.max.bytes` will reserve 512 MB 
for input buffering by default — set it explicitly on memory-constrained 
deployments.

Review Comment:
   Two wording issues: "reserve 512 MB" is misleading, it's a cap, nothing is 
allocated up front. And please state explicitly that when neither config is 
set, the previous implicit 1000 records/partition limit no longer applies.



##########
streams/src/main/java/org/apache/kafka/streams/processor/internals/DefaultStreamsRebalanceListener.java:
##########
@@ -86,6 +86,7 @@ public void onTasksRevoked(final 
Set<StreamsRebalanceData.TaskId> tasks) {
         if (streamThread.state() != StreamThread.State.PENDING_SHUTDOWN) {
             streamThread.setState(StreamThread.State.PARTITIONS_REVOKED);
         }
+        
streamThread.removePartitionsFromBufferOverflowTracking(partitionsToRevoke);

Review Comment:
   `onAllTasksLost` below should prune the tracking set too, for symmetry with 
`onPartitionsLost` in the classic listener.



##########
streams/src/main/java/org/apache/kafka/streams/StreamsConfig.java:
##########
@@ -463,8 +463,10 @@ public class StreamsConfig extends AbstractConfig {
     @SuppressWarnings("WeakerAccess")
     public static final String BOOTSTRAP_SERVERS_CONFIG = 
CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG;
 
-    /** {@code buffered.records.per.partition} */
+    /** {@code buffered.records.per.partition}
+     * @deprecated Since 4.4. Use {@link #INPUT_BUFFER_MAX_BYTES_CONFIG 
"input.buffer.max.bytes"} instead. */

Review Comment:
   Trunk is 4.5.0-SNAPSHOT and the 4.4 branch has been cut without this PR, so 
this should say 4.5, and the upgrade-guide paragraph should move to the 4.5.0 
section.



##########
streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java:
##########
@@ -1244,6 +1279,9 @@ void runOnceWithoutProcessingThreads() {
 
         final long pollLatency;
         taskManager.resumePollingForPartitionsWithAvailableSpace();

Review Comment:
   When only the deprecated config is set, both guards are active and they 
fight: `StreamTask.process` adds any partition with `queue.size() <= 
maxBufferedSize` to `partitionsToResume`, and this call resumes them 
unconditionally at the start of the next iteration, undoing the bytes-guard 
pause from the previous `pollPhase`. Net effect: resume -> fetch -> pause every 
iteration and the bytes cap never holds for exactly the migration population 
the upgrade note describes.
   
   The legacy resume path should skip partitions in 
`partitionsPausedForBufferOverflow` (e.g. pass the set down, or filter in 
`StreamTask.resumePollingForPartitionsWithAvailableSpace`).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to