You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jira@kafka.apache.org by "mumrah (via GitHub)" <gi...@apache.org> on 2023/12/13 22:19:45 UTC
[PR] KAFKA-16007 Re-batch records during the migration [kafka]
mumrah opened a new pull request, #15007:
URL: https://github.com/apache/kafka/pull/15007
To avoid creating excessively small KRaft batches during the ZK migration, this patch adds a mechanism to re-batch records into sizes of at least 1000. Migrations are now using metadata transactions, so the batch boundaries for migrated records are not important. Even in light of that, this implementation does not break existing batches. It will only combine them into a larger batch to meet the minimum size.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "hachikuji (via GitHub)" <gi...@apache.org>.
hachikuji commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1427463801
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java:
##########
@@ -645,6 +648,29 @@ public void run() throws Exception {
}
}
+ private BufferingBatchConsumer<ApiMessageAndVersion> buildMigrationBatchConsumer(
+ MigrationManifest.Builder manifestBuilder
+ ) {
+ return new BufferingBatchConsumer<>(batch -> {
+ try {
+ if (log.isTraceEnabled()) {
+ batch.forEach(apiMessageAndVersion ->
+ log.trace(recordRedactor.toLoggableString(apiMessageAndVersion.message())));
+ }
+ CompletableFuture<?> future = zkRecordConsumer.acceptBatch(batch);
Review Comment:
Does the consumer here have any expectation on atomicity of the records? I am trying to figure out how the batching applies at the raft layer. Would you expect the batches to be preserved in the log?
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/MigrationManifest.java:
##########
@@ -91,24 +101,42 @@ public long durationMs() {
return TimeUnit.NANOSECONDS.toMillis(durationNanos);
}
+ public double avgBatchDurationMs() {
+ if (totalBatches == 0) {
+ return -1;
+ }
+ return 1.0 * TimeUnit.NANOSECONDS.toMillis(totalBatchDurationsNs) / totalBatches;
+ }
+
+ public double avgBatchSize() {
+ if (totalBatches == 0) {
+ return -1;
+ }
+ return 1.0 * totalBatchSizes / totalBatches;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MigrationManifest that = (MigrationManifest) o;
return totalRecords == that.totalRecords &&
totalBatches == that.totalBatches &&
+ totalBatchDurationsNs == that.totalBatchDurationsNs &&
+ totalBatchSizes == that.totalBatchSizes &&
durationNanos == that.durationNanos &&
recordTypeCounts.equals(that.recordTypeCounts);
}
@Override
public int hashCode() {
- return Objects.hash(totalRecords, totalBatches, durationNanos, recordTypeCounts);
+ return Objects.hash(totalRecords, totalBatches, totalBatchDurationsNs, totalBatchSizes, durationNanos, recordTypeCounts);
}
public String toString() {
- return String.format("%d records were generated in %d ms across %d batches. The record types were %s",
- totalRecords, durationMs(), totalBatches, recordTypeCounts);
+ return String.format(
+ "%d records were generated in %d ms across %d batches. The average batch size was %.2f " +
Review Comment:
The "average batch size" might be a little ambiguous. Maybe we could say "record/batch" or something like that? Wondering if size in bytes is interesting also, but perhaps we can get that from the raft metrics.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1427480307
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java:
##########
@@ -645,6 +648,29 @@ public void run() throws Exception {
}
}
+ private BufferingBatchConsumer<ApiMessageAndVersion> buildMigrationBatchConsumer(
+ MigrationManifest.Builder manifestBuilder
+ ) {
+ return new BufferingBatchConsumer<>(batch -> {
+ try {
+ if (log.isTraceEnabled()) {
+ batch.forEach(apiMessageAndVersion ->
+ log.trace(recordRedactor.toLoggableString(apiMessageAndVersion.message())));
+ }
+ CompletableFuture<?> future = zkRecordConsumer.acceptBatch(batch);
Review Comment:
To answer your question more directly
> Does the consumer here have any expectation on atomicity of the records?
No. The eventual consumer of these batches is QuorumController#MigrationRecordConsumer which simply sends them along to Raft as a non-atomic batch. It doesn't care about batch boundaries or alignment.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on PR #15007:
URL: https://github.com/apache/kafka/pull/15007#issuecomment-1858418974
I filed https://issues.apache.org/jira/browse/KAFKA-16020 for the nanos issue. Good catch!
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "hachikuji (via GitHub)" <gi...@apache.org>.
hachikuji commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1427531359
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/BufferingBatchConsumer.java:
##########
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kafka.metadata.migration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A record batch consumer that merges incoming batches into batches of a minimum a given size. It does so
+ * by buffering the records into an array that is later flushed to a downstream consumer. Batches consumed
+ * by this class will not be broken apart, only combined with other batches to reach the minimum batch size.
+ * </p>
+ * Note that {@link #close()} must be called after the last batch has been accepted in order to flush any
+ * buffered records.
+ */
+public class BufferingBatchConsumer<T> implements Consumer<List<T>> {
+
+ private final Consumer<List<T>> delegateConsumer;
+ private final List<T> bufferedBatch;
+ private final int minBatchSize;
+
+ BufferingBatchConsumer(Consumer<List<T>> delegateConsumer, int minBatchSize) {
+ this.delegateConsumer = delegateConsumer;
+ this.bufferedBatch = new ArrayList<>(minBatchSize);
+ this.minBatchSize = minBatchSize;
+ }
+
+ @Override
+ public void accept(List<T> batch) {
+ bufferedBatch.addAll(batch);
+ if (bufferedBatch.size() >= minBatchSize) {
+ delegateConsumer.accept(new ArrayList<>(bufferedBatch));
+ bufferedBatch.clear();
+ }
+ }
+
+ public void close() {
Review Comment:
Yeah, I was thinking the same thing. Since there are no resources to actually close, I think `flush` would be a better name.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on PR #15007:
URL: https://github.com/apache/kafka/pull/15007#issuecomment-1857890630
Thanks for taking a look @jsancio! I'll answer some related questions here. If an error occurs inside the `readAllMetadata` call in KRaftMetadataDriver, the catch block will explicitly abort the transaction. If the controller crashes during this call or any time before committing the EndTransactionRecord, then the next active controller will abort the partial transaction. This is the same case as a controller not crashing but losing leadership during the migration (like from a timeout).
The logic for detecting and aborting partial transactions is in ActivationRecordsGenerator.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1428410182
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/MigrationManifest.java:
##########
@@ -41,15 +41,17 @@ public static class Builder {
private final Map<MetadataRecordType, Integer> counts = new HashMap<>();
private int batches = 0;
private int total = 0;
+ private long batchDurationsNs = 0;
private long endTimeNanos = 0;
Builder(Time time) {
this.time = time;
this.startTimeNanos = time.nanoseconds();
}
- public void acceptBatch(List<ApiMessageAndVersion> recordBatch) {
+ public void acceptBatch(List<ApiMessageAndVersion> recordBatch, long durationNs) {
batches++;
+ batchDurationsNs += durationNs;
Review Comment:
Yup, that's correct.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1427477149
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/MigrationManifest.java:
##########
@@ -91,24 +101,42 @@ public long durationMs() {
return TimeUnit.NANOSECONDS.toMillis(durationNanos);
}
+ public double avgBatchDurationMs() {
+ if (totalBatches == 0) {
+ return -1;
+ }
+ return 1.0 * TimeUnit.NANOSECONDS.toMillis(totalBatchDurationsNs) / totalBatches;
+ }
+
+ public double avgBatchSize() {
+ if (totalBatches == 0) {
+ return -1;
+ }
+ return 1.0 * totalBatchSizes / totalBatches;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MigrationManifest that = (MigrationManifest) o;
return totalRecords == that.totalRecords &&
totalBatches == that.totalBatches &&
+ totalBatchDurationsNs == that.totalBatchDurationsNs &&
+ totalBatchSizes == that.totalBatchSizes &&
durationNanos == that.durationNanos &&
recordTypeCounts.equals(that.recordTypeCounts);
}
@Override
public int hashCode() {
- return Objects.hash(totalRecords, totalBatches, durationNanos, recordTypeCounts);
+ return Objects.hash(totalRecords, totalBatches, totalBatchDurationsNs, totalBatchSizes, durationNanos, recordTypeCounts);
}
public String toString() {
- return String.format("%d records were generated in %d ms across %d batches. The record types were %s",
- totalRecords, durationMs(), totalBatches, recordTypeCounts);
+ return String.format(
+ "%d records were generated in %d ms across %d batches. The average batch size was %.2f " +
Review Comment:
Actually, after this patch we probably expect this value to be around 1000, so maybe it's not that useful to print out here.
Size is interesting, but yea we can infer that from the Raft metrics.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "jsancio (via GitHub)" <gi...@apache.org>.
jsancio commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1427486933
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/BufferingBatchConsumer.java:
##########
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kafka.metadata.migration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A record batch consumer that merges incoming batches into batches of a minimum a given size. It does so
+ * by buffering the records into an array that is later flushed to a downstream consumer. Batches consumed
+ * by this class will not be broken apart, only combined with other batches to reach the minimum batch size.
+ * </p>
+ * Note that {@link #close()} must be called after the last batch has been accepted in order to flush any
+ * buffered records.
+ */
+public class BufferingBatchConsumer<T> implements Consumer<List<T>> {
+
+ private final Consumer<List<T>> delegateConsumer;
+ private final List<T> bufferedBatch;
+ private final int minBatchSize;
+
+ BufferingBatchConsumer(Consumer<List<T>> delegateConsumer, int minBatchSize) {
+ this.delegateConsumer = delegateConsumer;
+ this.bufferedBatch = new ArrayList<>(minBatchSize);
+ this.minBatchSize = minBatchSize;
+ }
+
+ @Override
+ public void accept(List<T> batch) {
+ bufferedBatch.addAll(batch);
+ if (bufferedBatch.size() >= minBatchSize) {
+ delegateConsumer.accept(new ArrayList<>(bufferedBatch));
+ bufferedBatch.clear();
+ }
+ }
+
+ public void close() {
Review Comment:
Close doesn't seem to be correct semantic since:
1. This type doesn't use any resource outside of main memory
2. The user can continue to use the object after calling close.
I think a more accurate name generally used in Java is `flush`, `force` or `drain`.
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java:
##########
@@ -645,6 +648,29 @@ public void run() throws Exception {
}
}
+ private BufferingBatchConsumer<ApiMessageAndVersion> buildMigrationBatchConsumer(
+ MigrationManifest.Builder manifestBuilder
+ ) {
+ return new BufferingBatchConsumer<>(batch -> {
+ try {
+ if (log.isTraceEnabled()) {
+ batch.forEach(apiMessageAndVersion ->
+ log.trace(recordRedactor.toLoggableString(apiMessageAndVersion.message())));
+ }
+ CompletableFuture<?> future = zkRecordConsumer.acceptBatch(batch);
Review Comment:
Related question, what happens if KRaft loses leadership in the middle of this consumer loop?
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java:
##########
@@ -664,23 +690,12 @@ public void run() throws Exception {
super.handleException(t);
}
try {
- zkMigrationClient.readAllMetadata(batch -> {
- try {
- log.info("Migrating {} records from ZK", batch.size());
- if (log.isTraceEnabled()) {
- batch.forEach(apiMessageAndVersion ->
- log.trace(recordRedactor.toLoggableString(apiMessageAndVersion.message())));
- }
- CompletableFuture<?> future = zkRecordConsumer.acceptBatch(batch);
- FutureUtils.waitWithLogging(KRaftMigrationDriver.this.log, "",
- "the metadata layer to commit migration record batch",
- future, Deadline.fromDelay(time, METADATA_COMMIT_MAX_WAIT_MS, TimeUnit.MILLISECONDS), time);
- manifestBuilder.acceptBatch(batch);
- } catch (Throwable e) {
- // This will cause readAllMetadata to throw since this batch consumer is called directly from readAllMetadata
- throw new RuntimeException(e);
- }
- }, brokersInMetadata::add);
+ BufferingBatchConsumer<ApiMessageAndVersion> migrationBatchConsumer = buildMigrationBatchConsumer(manifestBuilder);
+ zkMigrationClient.readAllMetadata(
+ migrationBatchConsumer,
+ brokersInMetadata::add
+ );
+ migrationBatchConsumer.close();
Review Comment:
If `zkMigrationClient.readAllMetadata` throws `migrationBatchConsumer.close` is not called. Is this okay because `zkRecordConsumer.abortMigration` is called in the `catch`?
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java:
##########
@@ -645,6 +648,29 @@ public void run() throws Exception {
}
}
+ private BufferingBatchConsumer<ApiMessageAndVersion> buildMigrationBatchConsumer(
+ MigrationManifest.Builder manifestBuilder
+ ) {
+ return new BufferingBatchConsumer<>(batch -> {
+ try {
+ if (log.isTraceEnabled()) {
+ batch.forEach(apiMessageAndVersion ->
+ log.trace(recordRedactor.toLoggableString(apiMessageAndVersion.message())));
+ }
+ CompletableFuture<?> future = zkRecordConsumer.acceptBatch(batch);
+ long batchStart = time.nanoseconds();
+ FutureUtils.waitWithLogging(KRaftMigrationDriver.this.log, "",
Review Comment:
In general, Kafka should avoid blocking on a CompletableFuture. This can be avoided by using `CompletableFuture::thenCompose` or better yet `concurrent.Flow` since the `CompletableFuture` doesn't return an interesting value.
I looked at `ZkMigrationClient`. If you wanted to use `Flow`. You would replace the use of `Consumer` with `Flow.Subscriber`. `ZkMigrationClient` would be come a `Flow.Publisher`.
Flow has support for pipelining and back-pressure. For example, you would make initial `Subscription.request` `1000` and request more data as the `zkRecordConsumer` processes more batches.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "jsancio (via GitHub)" <gi...@apache.org>.
jsancio commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1428364538
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/MigrationManifest.java:
##########
@@ -41,15 +41,17 @@ public static class Builder {
private final Map<MetadataRecordType, Integer> counts = new HashMap<>();
private int batches = 0;
private int total = 0;
+ private long batchDurationsNs = 0;
private long endTimeNanos = 0;
Builder(Time time) {
this.time = time;
this.startTimeNanos = time.nanoseconds();
}
- public void acceptBatch(List<ApiMessageAndVersion> recordBatch) {
+ public void acceptBatch(List<ApiMessageAndVersion> recordBatch, long durationNs) {
batches++;
+ batchDurationsNs += durationNs;
Review Comment:
Okay. This is measuring how much time the ZK migration spent in the controller, writing and committing the batches to KRaft, right?
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1427972960
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/BufferingBatchConsumer.java:
##########
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kafka.metadata.migration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A record batch consumer that merges incoming batches into batches of a minimum a given size. It does so
+ * by buffering the records into an array that is later flushed to a downstream consumer. Batches consumed
+ * by this class will not be broken apart, only combined with other batches to reach the minimum batch size.
+ * </p>
+ * Note that {@link #close()} must be called after the last batch has been accepted in order to flush any
+ * buffered records.
+ */
+public class BufferingBatchConsumer<T> implements Consumer<List<T>> {
+
+ private final Consumer<List<T>> delegateConsumer;
+ private final List<T> bufferedBatch;
+ private final int minBatchSize;
+
+ BufferingBatchConsumer(Consumer<List<T>> delegateConsumer, int minBatchSize) {
+ this.delegateConsumer = delegateConsumer;
+ this.bufferedBatch = new ArrayList<>(minBatchSize);
+ this.minBatchSize = minBatchSize;
+ }
+
+ @Override
+ public void accept(List<T> batch) {
+ bufferedBatch.addAll(batch);
+ if (bufferedBatch.size() >= minBatchSize) {
+ delegateConsumer.accept(new ArrayList<>(bufferedBatch));
+ bufferedBatch.clear();
+ }
+ }
+
+ public void close() {
Review Comment:
Yea, good point. I'll rename this to flush
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "jsancio (via GitHub)" <gi...@apache.org>.
jsancio commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1428347367
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/BufferingBatchConsumer.java:
##########
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kafka.metadata.migration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A record batch consumer that merges incoming batches into batches of a minimum a given size. It does so
+ * by buffering the records into an array that is later flushed to a downstream consumer. Batches consumed
+ * by this class will not be broken apart, only combined with other batches to reach the minimum batch size.
+ * </p>
+ * Note that {@link #flush()} must be called after the last batch has been accepted in order to flush any
+ * buffered records.
+ */
+public class BufferingBatchConsumer<T> implements Consumer<List<T>> {
+
+ private final Consumer<List<T>> delegateConsumer;
+ private final List<T> bufferedBatch;
+ private final int minBatchSize;
+
+ BufferingBatchConsumer(Consumer<List<T>> delegateConsumer, int minBatchSize) {
+ this.delegateConsumer = delegateConsumer;
+ this.bufferedBatch = new ArrayList<>(minBatchSize);
+ this.minBatchSize = minBatchSize;
+ }
+
+ @Override
+ public void accept(List<T> batch) {
+ bufferedBatch.addAll(batch);
+ if (bufferedBatch.size() >= minBatchSize) {
+ delegateConsumer.accept(new ArrayList<>(bufferedBatch));
+ bufferedBatch.clear();
Review Comment:
How bout this implication?
```java
delegateConsumer.accept(bufferedBatch);
bufferedBatch = new ArrayList<>(minBatchSize);
```
Similar in `flush`. There seems to be some code duplication between these two methods.
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java:
##########
@@ -645,6 +648,29 @@ public void run() throws Exception {
}
}
+ private BufferingBatchConsumer<ApiMessageAndVersion> buildMigrationBatchConsumer(
+ MigrationManifest.Builder manifestBuilder
+ ) {
+ return new BufferingBatchConsumer<>(batch -> {
+ try {
+ if (log.isTraceEnabled()) {
+ batch.forEach(apiMessageAndVersion ->
+ log.trace(recordRedactor.toLoggableString(apiMessageAndVersion.message())));
+ }
+ CompletableFuture<?> future = zkRecordConsumer.acceptBatch(batch);
+ long batchStart = time.nanoseconds();
+ FutureUtils.waitWithLogging(KRaftMigrationDriver.this.log, "",
Review Comment:
That's fair. I keep forgetting that we still need to support Java 8. Looking forward to 4.x.
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/MigrationManifest.java:
##########
@@ -41,15 +41,17 @@ public static class Builder {
private final Map<MetadataRecordType, Integer> counts = new HashMap<>();
private int batches = 0;
private int total = 0;
+ private long batchDurationsNs = 0;
private long endTimeNanos = 0;
Builder(Time time) {
this.time = time;
this.startTimeNanos = time.nanoseconds();
}
- public void acceptBatch(List<ApiMessageAndVersion> recordBatch) {
+ public void acceptBatch(List<ApiMessageAndVersion> recordBatch, long durationNs) {
batches++;
+ batchDurationsNs += durationNs;
Review Comment:
Okay. This is measure how much time the ZK migration spent in the controller, writing and committing the batches to KRaft, right?
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java:
##########
@@ -645,6 +648,29 @@ public void run() throws Exception {
}
}
+ private BufferingBatchConsumer<ApiMessageAndVersion> buildMigrationBatchConsumer(
+ MigrationManifest.Builder manifestBuilder
+ ) {
+ return new BufferingBatchConsumer<>(batch -> {
+ try {
+ if (log.isTraceEnabled()) {
+ batch.forEach(apiMessageAndVersion ->
+ log.trace(recordRedactor.toLoggableString(apiMessageAndVersion.message())));
+ }
+ CompletableFuture<?> future = zkRecordConsumer.acceptBatch(batch);
+ long batchStart = time.nanoseconds();
+ FutureUtils.waitWithLogging(KRaftMigrationDriver.this.log, "",
Review Comment:
This is an existing issue but `Time.waitForFuture` doesn't look correct. It is comparing nano times. In the JVM you can't compare nano times because they can overflow. It is recommended to instead compare elapse time: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html#nanoTime()
This will cause this code to block forever when it overflows.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah merged PR #15007:
URL: https://github.com/apache/kafka/pull/15007
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on PR #15007:
URL: https://github.com/apache/kafka/pull/15007#issuecomment-1858638896
Test failures look unrelated
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1427975054
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java:
##########
@@ -645,6 +648,29 @@ public void run() throws Exception {
}
}
+ private BufferingBatchConsumer<ApiMessageAndVersion> buildMigrationBatchConsumer(
+ MigrationManifest.Builder manifestBuilder
+ ) {
+ return new BufferingBatchConsumer<>(batch -> {
+ try {
+ if (log.isTraceEnabled()) {
+ batch.forEach(apiMessageAndVersion ->
+ log.trace(recordRedactor.toLoggableString(apiMessageAndVersion.message())));
+ }
+ CompletableFuture<?> future = zkRecordConsumer.acceptBatch(batch);
+ long batchStart = time.nanoseconds();
+ FutureUtils.waitWithLogging(KRaftMigrationDriver.this.log, "",
Review Comment:
Thanks, the Flow API looks really cool. I'll check that out. Does look like it's Java 9+ only, but I'll keep it in mind for future stuff (I think we'll be bumping up to Java 11 for 4.0)
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Merge batch records during ZK migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on code in PR #15007:
URL: https://github.com/apache/kafka/pull/15007#discussion_r1427472332
##########
metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java:
##########
@@ -645,6 +648,29 @@ public void run() throws Exception {
}
}
+ private BufferingBatchConsumer<ApiMessageAndVersion> buildMigrationBatchConsumer(
+ MigrationManifest.Builder manifestBuilder
+ ) {
+ return new BufferingBatchConsumer<>(batch -> {
+ try {
+ if (log.isTraceEnabled()) {
+ batch.forEach(apiMessageAndVersion ->
+ log.trace(recordRedactor.toLoggableString(apiMessageAndVersion.message())));
+ }
+ CompletableFuture<?> future = zkRecordConsumer.acceptBatch(batch);
Review Comment:
Since this consumer only combines batches, any semantics relying on batch boundaries should be ok. Anyways, batches are irrelevant during the migration since we're using transactions at the controller layer
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] KAFKA-16007 Re-batch records during the migration [kafka]
Posted by "mumrah (via GitHub)" <gi...@apache.org>.
mumrah commented on PR #15007:
URL: https://github.com/apache/kafka/pull/15007#issuecomment-1856887268
Some supporting evidence.
I modified ZkMigrationIntegrationTest to create 1000 partitions in three ways (10 topics, 100 topics, 1000 topics). The number of batches generated and the overall migration time was pretty linear:
| Case | Records | Batches | Time Per Batch | Total Time |
| -- | -- | -- | -- | -- |
| 10 topics x 100 partitions (trunk) | 1020 | 12 | 32.33 | 536 |
| 100 topics x 10 partitions (trunk) | 1110 | 102 | 30.30 | 3317 |
| 1000 topics x 1 partition (trunk) | 2010 | 1002 | 30.69 | 32243 |
| 1000 topics x 1 partition (this PR) | 2010 | 3 | 30.69 | 415 |
times in milliseconds.
As we can see, the batch size doesn't really affect the time waited on each batch. The biggest gain here comes from making fewer batches overall. In KRaftMigrationDriver, we wait for each batch to be committed by the controller which really limits throughput. Probably this ~30ms we're seeing is the round-trip time in the Raft layer.
--
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: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org