aliehsaeedii commented on code in PR #22570:
URL: https://github.com/apache/kafka/pull/22570#discussion_r3532075182


##########
streams/src/main/java/org/apache/kafka/streams/processor/StreamPartitioner.java:
##########
@@ -53,17 +54,23 @@
 @FunctionalInterface
 public interface StreamPartitioner<K, V> {
 
+    @Deprecated(since = "4.4", forRemoval = true)
+    Optional<Set<Integer>> partitions(String topic, K key, V value, int 
numPartitions);

Review Comment:
   The deprecated method lost its javadoc entirely (it moved to the new 
overload). Since this stays part of the public API until removal, it should 
keep a `@deprecated` javadoc tag pointing at the replacement, e.g.:
   ```java
   /**
    * @deprecated Since 4.4. Use {@link #partitions(String, Object, Object, 
Headers, int)} instead.
    */
   ```
   This also answers the question from the other thread: yes, the javadoc tag 
is expected alongside the annotation.



##########
streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/KafkaStreamsNamedTopologyWrapper.java:
##########
@@ -416,15 +417,15 @@ public Collection<StreamsMetadata> 
allStreamsClientsMetadataForTopology(final St
     }
 
     /**
-     * See {@link KafkaStreams#queryMetadataForKey(String, Object, Serializer)}
+     * See {@link KafkaStreams#queryMetadataForKey(String, Object, Headers, 
Serializer)}

Review Comment:
   `Headers` is referenced in this `{@link}` but only 
`org.apache.kafka.common.header.internals.RecordHeaders` was imported, so the 
javadoc reference won't resolve. Please import 
`org.apache.kafka.common.header.Headers` as well (or fully qualify it in the 
link).



##########
streams/src/main/java/org/apache/kafka/streams/kstream/internals/WindowedStreamPartitioner.java:
##########
@@ -33,21 +34,28 @@ public WindowedStreamPartitioner(final 
WindowedSerializer<K> serializer) {
         this.serializer = serializer;
     }
 
+    @SuppressWarnings({"removal"})
+    @Override
+    public Optional<Set<Integer>> partitions(final String topic, final 
Windowed<K> windowedKey, final V value, final int numPartitions) {
+        return partitions(topic, windowedKey, value, new RecordHeaders(), 
numPartitions);
+    }
+
     /**
      * WindowedStreamPartitioner determines the partition number for a record 
with the given windowed key and value
      * and the current number of partitions. The partition number id 
determined by the original key of the windowed key
      * using the same logic as DefaultPartitioner so that the topic is 
partitioned by the original key.
      *
-     * @param topic the topic name this record is sent to
-     * @param windowedKey the key of the record
-     * @param value the value of the record
+     * @param topic         the topic name this record is sent to
+     * @param windowedKey   the key of the record
+     * @param value         the value of the record
+     * @param headers       the record headers
      * @param numPartitions the total number of partitions
      * @return an integer between 0 and {@code numPartitions-1}, or {@code 
null} if the default partitioning logic should be used
      */
     @Override
-    public Optional<Set<Integer>> partitions(final String topic, final 
Windowed<K> windowedKey, final V value, final int numPartitions) {
+    public Optional<Set<Integer>> partitions(final String topic, final 
Windowed<K> windowedKey, final V value, final Headers headers, final int 
numPartitions) {
         // for windowed key, the key bytes should never be null
-        final byte[] keyBytes = serializer.serializeBaseKey(topic, new 
RecordHeaders(), windowedKey);
+        final byte[] keyBytes = serializer.serializeBaseKey(topic, headers, 
windowedKey);

Review Comment:
   Behavioral note worth confirming against the KIP: previously the base key 
was always serialized with empty `RecordHeaders`; now it gets the real record 
headers. For applications whose windowed key serializer is header-sensitive, 
the partition assignment of windowed sink/repartition records silently changes 
after upgrading, which can break co-partitioning with data written before the 
upgrade. If the KIP already covers this, it should at least be called out in 
the upgrade notes / streams docs PR you mentioned.



##########
streams/src/main/java/org/apache/kafka/streams/KafkaStreams.java:
##########
@@ -1735,38 +1737,66 @@ public Collection<StreamsMetadata> 
streamsMetadataForStore(final String storeNam
         return streamsMetadataState.allMetadataForStore(storeName);
     }
 
+    /**
+     * Finds the metadata containing the active hosts and standby hosts where 
the key being queried would reside,
+     * without requiring record headers to be provided.
+     * <p>
+     * See {@link #queryMetadataForKey(String, Object, Headers, Serializer)} 
for more details.
+     */
+    public <K> KeyQueryMetadata queryMetadataForKey(final String storeName,
+                                                    final K key,
+                                                    final Serializer<K> 
keySerializer) {
+        return queryMetadataForKey(storeName, key, new RecordHeaders(), 
keySerializer);

Review Comment:
   For users whose partitioner (or key serializer) is header-aware, this 
convenience overload computes the partition from *empty* headers, so it can 
locate the wrong host for keys that were written with different headers. Worth 
a sentence in the javadoc of both no-headers overloads: "if your partitioner or 
serializer makes use of headers, use the `Headers` overload, otherwise the 
returned metadata may not match where the key actually resides."



##########
streams/src/main/java/org/apache/kafka/streams/processor/StreamPartitioner.java:
##########
@@ -53,17 +54,23 @@
 @FunctionalInterface
 public interface StreamPartitioner<K, V> {
 
+    @Deprecated(since = "4.3", forRemoval = true)
+    Optional<Set<Integer>> partitions(String topic, K key, V value, int 
numPartitions);
+
     /**
-     * Determine the number(s) of the partition(s) to which a record with the 
given key and value should be sent, 
+     * Determine the number(s) of the partition(s) to which a record with the 
given key and value should be sent,
      * for the given topic and current partition count
      * @param topic the topic name this record is sent to
      * @param key the key of the record
      * @param value the value of the record
+     * @param headers the record headers
      * @param numPartitions the total number of partitions
      * @return an Optional of Set of integers between 0 and {@code 
numPartitions-1},
      * Empty optional means using default partitioner
      * Optional of an empty set means the record won't be sent to any 
partitions i.e drop it.
      * Optional of Set of integers means the partitions to which the record 
should be sent to.
      * */
-    Optional<Set<Integer>> partitions(String topic, K key, V value, int 
numPartitions);
+    default Optional<Set<Integer>> partitions(final String topic, final K key, 
final V value, final Headers headers, final int numPartitions) {

Review Comment:
   Thanks — agreed. Fine to keep as is. +1 on documenting the lambda limitation 
in the streams docs in a separate PR — good that it is tracked on the ticket:)



##########
streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorTopologyTest.java:
##########
@@ -1074,7 +1074,13 @@ private void assertNextOutputRecord(final 
TestRecord<String, String> record,
     }
 
     private StreamPartitioner<String, String> constantPartitioner(final 
Integer partition) {
-        return (topic, key, value, numPartitions) -> 
Optional.of(Collections.singleton(partition));
+        return new StreamPartitioner<>() {
+            @SuppressWarnings("removal")
+            @Override
+            public Optional<Set<Integer>> partitions(final String topic, final 
String key, final String value, final int numPartitions) {
+                return Optional.of(Collections.singleton(partition));
+            }
+        };
     }
 

Review Comment:
   Since the KIP's core compatibility promise is exactly that existing lambda 
implementations keep working, should we consider keeping at least one 
lambda-based `StreamPartitioner` in the tests so that promise stays guarded at 
compile time and runtime. (Converting to an anonymous class is of course right 
for the tests that assert the deprecated method is NOT called.)



##########
streams/src/main/java/org/apache/kafka/streams/processor/StreamPartitioner.java:
##########
@@ -53,17 +54,23 @@
 @FunctionalInterface
 public interface StreamPartitioner<K, V> {
 
+    @Deprecated(since = "4.3", forRemoval = true)

Review Comment:
   yes to the javadoc: after moving the docs to the new overload this method 
has no javadoc at all, and the generated API docs expect a `@deprecated` tag 
alongside the annotation. Something like: `@deprecated Since 4.4. Use {@link 
#partitions(String, Object, Object, Headers, int)} instead.` Re mentioning 
removal: "will be removed in a future major release".



##########
streams/src/test/java/org/apache/kafka/streams/kstream/internals/WindowedStreamPartitionerTest.java:
##########
@@ -81,4 +82,29 @@ public void testCopartitioning() {
             }
         }
     }
+
+    @Test
+    public void testCopartitioningWithHeaders() {
+        final Random rand = new Random();
+        final org.apache.kafka.common.header.Headers headers = new 
org.apache.kafka.common.header.internals.RecordHeaders();

Review Comment:
   nit: use imports for `Headers`/`RecordHeaders` instead of fully-qualified 
names inline — consistent with the rest of the codebase (and with 
`DefaultStreamPartitionerTest` in this same PR).



##########
streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamsMetadataState.java:
##########
@@ -239,13 +244,15 @@ public synchronized <K> KeyQueryMetadata 
keyQueryMetadataForKey(final String sto
      *
      * @param storeName   Name of the store
      * @param key         Key to use
+     * @param headers     the record headers
      * @param partitioner partitioner to use to find correct partition for key
      * @param <K>         key type
      * @return The {@link KeyQueryMetadata} for the storeName and key or 
{@link KeyQueryMetadata#NOT_AVAILABLE}
      * if streams is (re-)initializing, or {@code null} if no matching 
metadata could be found.
      */
     public synchronized <K> KeyQueryMetadata keyQueryMetadataForKey(final 
String storeName,
                                                                     final K 
key,

Review Comment:
   `storeName`, `key`, and `partitioner`/`keySerializer` are all null-checked 
here, but the new `headers` parameter is not. A caller passing `null` via 
`KafkaStreams#queryMetadataForKey(store, key, null, serializer)` would flow 
`null` into header-aware partitioners/serializers and NPE deep inside user 
code. Consider either `Objects.requireNonNull(headers, "headers can't be 
null")` or normalizing `null` to `new RecordHeaders()` for consistency with the 
no-headers overloads.



-- 
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