Skip to content

KAFKA-20976: Make max partitions per request configurable - #23245

Open
andrewgrantcflt wants to merge 8 commits into
apache:trunkfrom
andrewgrantcflt:andrewgrantcflt-KAFKA-20976
Open

KAFKA-20976: Make max partitions per request configurable#23245
andrewgrantcflt wants to merge 8 commits into
apache:trunkfrom
andrewgrantcflt:andrewgrantcflt-KAFKA-20976

Conversation

@andrewgrantcflt

@andrewgrantcflt andrewgrantcflt commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Description

In ReplicationControlManager there is a max of 10,000 partitions
allowed in a single create topics request. In an app I am seeing this
limit being hit. I am trying to reproduce the error in an integration
test, but by having to create so many partitions the test is slow and
flaky. I have to create up to 10,000 partitions to reproduce the issue
because the limit is currently hard-coded.

So here I am simply making the limit a config. By having it as a config,
we can configure the limit to be lower and reproduce the issue in a test
without having to create so many partitions.

In fact, the current limit of 10,000 comes from the fact that we should
only add 10,000 records to a single batch that gets persisted to the
metadata log. We use that same 10,000 limit in a few other places, such
as when handling DELETE_TOPICS. There we use MAX_RECORDS_PER_USER_OP
when creating a bounded array. Ultimately all of these limits are
related in that we want to not add more than 10,000 records to a single
batch. So I refactor some code that uses that same limit to use the same
config I have added.

https: //issues.apache.org/jira/browse/KAFKA-20976 Reviewers: Andrew
Schofield aschofield@confluent.io

Reviewers: Andrew Schofield aschofield@confluent.io

@github-actions github-actions Bot added triage PRs from the community core Kafka Broker kraft small Small PRs labels Aug 23, 2026
public static final long CONTROLLER_PERFORMANCE_ALWAYS_LOG_THRESHOLD_MS_DEFAULT = 2000;
public static final String CONTROLLER_PERFORMANCE_ALWAYS_LOG_THRESHOLD_MS_DOC = "We will log an error message about controller events that take longer than this threshold.";

public static final String CONTROLLER_MAX_PARTITIONS_PER_BATCH_CONFIG = "controller.max.partitions.per.batch";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered naming the config controller.max.partitions.per.create.topics.batch as well. As of now, this config is only used when handling CREATE_TOPICS requests so arguably that name may be clearer. controller.max.partitions.per.batch is a bit generic and could imply it is used for other RPCs, but maybe that's better if we want to reuse this config in the future, for example for CREATE_PARTITIONS?

Curious to hear other folks' opinions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put the config in KRaftConfigs since it's only to be used by the controller.

@andrewgrantcflt andrewgrantcflt changed the title KAFKA-20976: Make KRaft max partitions per request limit a config KAFKA-20976: Make max partitions per request configurable Aug 23, 2026
@github-actions github-actions Bot removed the triage PRs from the community label Aug 24, 2026

@AndrewJSchofield AndrewJSchofield left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. I don't like adding the config without a KIP, but this is an internal-only config and it is making a hard-coded limit configurable. I reckon it sneaks in under the KIP bar :)

I do suggest making it a bit more general-purposes in the ReplicationControlManager. For example, deleteTopics and others limit the request size to MAX_RECORDS_PER_USER_OP, which is also 10000. Aren't these essentially the same thing, checking that the user operation is not too complex for the controller to handle? Let me know if that makes sense too.

public static final String CONTROLLER_MAX_PARTITIONS_PER_BATCH_CONFIG = "controller.max.partitions.per.batch";
public static final int CONTROLLER_MAX_PARTITIONS_PER_BATCH_DEFAULT = 10_000;
public static final String CONTROLLER_MAX_PARTITIONS_PER_BATCH_DOC = "The maximum number of partitions that the " +
"active controller will allow to be created by a single CreateTopics request. This limit protects the " +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a bit less specific by removing the "CreateTopics" part of this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Updated, especially as now the config is used in other places where we had the 10,000 limit for the max number of metadata records we can add to a single batch.

@andrewgrantcflt

Copy link
Copy Markdown
Contributor Author

Thanks for the PR. I don't like adding the config without a KIP, but this is an internal-only config and it is making a hard-coded limit configurable. I reckon it sneaks in under the KIP bar :)

I do suggest making it a bit more general-purposes in the ReplicationControlManager. For example, deleteTopics and others limit the request size to MAX_RECORDS_PER_USER_OP, which is also 10000. Aren't these essentially the same thing, checking that the user operation is not too complex for the controller to handle? Let me know if that makes sense too.

@AndrewJSchofield and I chatted. I am going to close this PR because it's a bit controversial adding a config without a KIP.

@AndrewJSchofield

Copy link
Copy Markdown
Member

This area is ripe for a KIP as part of the ongoing work to harden the Kafka protocol.

@github-actions github-actions Bot added the triage PRs from the community label Aug 24, 2026
@andrewgrantcflt

Copy link
Copy Markdown
Contributor Author

Re-opening the PR. After chatting with @AndrewJSchofield, @jsancio and @rajinisivaram, it should be OK to add an internal config without a KIP if we include an integration test showing it in use.

@github-actions github-actions Bot removed the small Small PRs label Aug 24, 2026
@github-actions github-actions Bot removed the triage PRs from the community label Aug 25, 2026
@AndrewJSchofield

Copy link
Copy Markdown
Member

@andrewgrantcflt Checkstyle failure

@andrewgrantcflt

Copy link
Copy Markdown
Contributor Author

@andrewgrantcflt Checkstyle failure

Thanks, fixing it.

@AndrewJSchofield AndrewJSchofield left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updated PR. It's good to see all of the related operations use the same config now. Just a few comments, but it's getting there.

setQuorumFeatures(quorumFeatures).
setDefaultReplicationFactor(config.defaultReplicationFactor.toShort).
setDefaultNumPartitions(config.numPartitions.intValue()).
setMaxRecordsPerBatch(config.controllerMaxRecordsPerBatch).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Any reason why this config is not added at the end, preserving the order after controllerPerformanceAlwaysLogThresholdMs?

@andrewgrantcflt andrewgrantcflt Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm no good reason. I will move it.

}
if (totalPartitions > MAX_PARTITIONS_PER_BATCH) {
if (totalPartitions > maxRecordsPerBatch) {
throw new PolicyViolationException("Excessively large number of partitions per request.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exception message is really not that elegant. It sounds like the configuration for the number of partitions per request is excessive, but actually it's just complaining about an individual request. How about something like "Too many partitions in request".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree. Is it OK to change the error message? I know users probably shouldn't be depending on the message, but in practice it inevitably will happen.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can change it.

ControllerResult<ElectLeadersResponseData> electLeaders(ElectLeadersRequestData request) {
ElectionType electionType = electionType(request.electionType());
List<ApiMessageAndVersion> records = BoundedList.newArrayBacked(MAX_RECORDS_PER_USER_OP);
List<ApiMessageAndVersion> records = BoundedList.newArrayBacked(maxRecordsPerBatch);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an observation from reading an unfamiliar area of code. This is going to result in a BoundedListTooLongException if the list capacity is exceeded, I think. Any reason why validateTotalNumberOfPartitions is taking a different path?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too sure. I do see the following:
https://github.com/apache/kafka/blob/trunk/metadata/src/main/java/org/apache/kafka/controller/errors/EventHandlerExceptionInfo.java#L83-L88

        } else if (internal instanceof BoundedListTooLongException) {
            // The operation could not be performed because it would have created an overly large
            // batch.
            return new EventHandlerExceptionInfo(false, false, internal,
                new PolicyViolationException("Unable to perform excessively large batch " +
                    "operation."));

I think BoundedListTooLongException does eventually get mapped to the same exception, albeit with a different error message. I probably would have made the code that throws BoundedListTooLongException catch it and re-throw a PolicyViolationException directly but that's probably out of scope for this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants