Skip to content

[CORE-15271] kafka/server: expose user based client quotas#29425

Merged
IoannisRP merged 4 commits into
redpanda-data:devfrom
IoannisRP:CORE-15271/user-based-quotas-kafka-api
Jan 29, 2026
Merged

[CORE-15271] kafka/server: expose user based client quotas#29425
IoannisRP merged 4 commits into
redpanda-data:devfrom
IoannisRP:CORE-15271/user-based-quotas-kafka-api

Conversation

@IoannisRP

@IoannisRP IoannisRP commented Jan 27, 2026

Copy link
Copy Markdown
Contributor

Implements : CORE-15271

Expose entity type user through the describe/alter client quota kafka api.

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v25.3.x
  • v25.2.x
  • v25.1.x

Release Notes

Features

  • Implement user-based client quotas.

Copilot AI review requested due to automatic review settings January 27, 2026 19:30
@IoannisRP IoannisRP self-assigned this Jan 27, 2026
@IoannisRP IoannisRP requested review from pgellert and removed request for Copilot January 27, 2026 19:30
@IoannisRP IoannisRP requested review from a team and Copilot January 27, 2026 19:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR implements user-based client quotas for Redpanda, enabling the Kafka API to support quota management at the user (principal) level in addition to the existing client-id based quotas.

Changes:

  • Added support for user entity type in Kafka client quota APIs (describe/alter)
  • Wired up user principal tracking throughout the quota system
  • Implemented feature flag gating for user quotas to support graceful upgrades
  • Added comprehensive test coverage for user quota functionality

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/rptest/tests/quota_management_test.py Added test cases for user quotas, compound user+client quotas, upgrade scenarios, and comparison operators for entity types
tests/rptest/clients/kcl.py Added optional node parameter to raw_alter_quotas for routing requests during upgrade tests
src/v/kafka/server/server.cc Wired user principal into partition mutation quota tracking for delete_topics
src/v/kafka/server/handlers/create_topics.cc Wired user principal into partition mutation quota tracking for create_topics
src/v/kafka/server/handlers/create_partitions.cc Wired user principal into partition mutation quota tracking for create_partitions
src/v/kafka/server/handlers/client_quotas.cc Implemented user entity type support in describe/alter quota handlers with feature flag gating
src/v/kafka/server/connection_context.h Made get_principal() public to enable quota tracking by user
src/v/kafka/server/connection_context.cc Wired user principal into fetch/produce throughput quota tracking
src/v/features/feature_table.h Added user_based_client_quota feature flag
src/v/features/feature_table.cc Added string representation for user_based_client_quota feature

Comment thread src/v/kafka/server/handlers/client_quotas.cc Outdated
Comment thread src/v/kafka/server/handlers/client_quotas.cc Outdated
@IoannisRP IoannisRP force-pushed the CORE-15271/user-based-quotas-kafka-api branch from 50f6274 to 3608650 Compare January 27, 2026 19:34
@IoannisRP IoannisRP requested a review from Copilot January 27, 2026 19:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Comment on lines +56 to +60
def _get_ordering(self):
return {name: idx for idx, name in enumerate(self.__class__)}

def __lt__(self, other):
ordering = self._get_ordering()

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

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

The method name _get_ordering uses a single leading underscore, which in Python typically indicates a 'private' method. However, this method is called by the public __lt__ method and constructs ordering for comparison operations. Consider renaming it to something more descriptive like _create_enum_ordering or _build_ordering_dict to better convey its purpose of creating an ordering dictionary from the enum class.

Suggested change
def _get_ordering(self):
return {name: idx for idx, name in enumerate(self.__class__)}
def __lt__(self, other):
ordering = self._get_ordering()
def _build_ordering_dict(self):
return {name: idx for idx, name in enumerate(self.__class__)}
def __lt__(self, other):
ordering = self._build_ordering_dict()

Copilot uses AI. Check for mistakes.

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 like that you felt then need to point out that it has a leading underscore and it is bad, but also to suggest another name with leading underscore! 😅

Comment on lines +330 to +335
bool has_feature_user_quota(const request_context& ctx) {
constexpr auto feature = features::feature::user_based_client_quota;
const auto& ft = ctx.feature_table().local();
return ft.is_active(feature);
}

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

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

The function name has_feature_user_quota is inconsistent with the actual feature name user_based_client_quota. Consider renaming to has_user_based_client_quota or has_feature_user_based_client_quota to match the feature naming convention.

Suggested change
bool has_feature_user_quota(const request_context& ctx) {
constexpr auto feature = features::feature::user_based_client_quota;
const auto& ft = ctx.feature_table().local();
return ft.is_active(feature);
}
bool has_user_based_client_quota(const request_context& ctx) {
constexpr auto feature = features::feature::user_based_client_quota;
const auto& ft = ctx.feature_table().local();
return ft.is_active(feature);
}
bool has_feature_user_quota(const request_context& ctx) {
return has_user_based_client_quota(ctx);
}

Copilot uses AI. Check for mistakes.
return ft.is_active(feature);
}

constexpr auto has_user = [](const auto& e) { return e.entity_type == "user"; };

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

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

The lambda has_user is defined in the anonymous namespace but its name is too generic for namespace-level scope. Consider making it a local variable in the functions where it's used (lines 371, 502), or rename it to be more specific like is_user_entity_type to better indicate it's checking the entity_type field.

Suggested change
constexpr auto has_user = [](const auto& e) { return e.entity_type == "user"; };
constexpr auto is_user_entity_type = [](const auto& e) {
return e.entity_type == "user";
};

Copilot uses AI. Check for mistakes.
@IoannisRP IoannisRP force-pushed the CORE-15271/user-based-quotas-kafka-api branch from 3608650 to af744d8 Compare January 27, 2026 19:39
@vbotbuildovich

Copy link
Copy Markdown
Collaborator

CI test results

test results on build#79721
test_class test_method test_arguments test_kind job_url test_status passed reason test_history
TestReadReplicaService test_identical_lwms_after_delete_records {"cloud_storage_type": 1, "partition_count": 5} integration https://buildkite.com/redpanda/redpanda/builds/79721#019c0112-88d5-42fb-82b2-46b605bda0e0 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0197, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=TestReadReplicaService&test_method=test_identical_lwms_after_delete_records

Comment thread src/v/kafka/server/handlers/client_quotas.cc Outdated
// either there's only a single key part or the key is a user+client
// compound key
if (entity.size() != 1) {
if (entity.size() > 2) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need to check for entity.size() == 0 as well here?

schema_registry_authz = 1ULL << 3U,
topic_ids_api = 1ULL << 4U,
controller_forced_reconfiguration = 1ULL << 5U,
user_based_client_quota = 1ULL << 6U,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be 13? What was 6 used for previously?

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.

6 used to be license in v24.3.x. 13 was replication_factor_change in the same version. I see more values that have been reused. 5 used to be serde_raft_0 and now it is controller_forced_reconfiguration

So it should be fine to reuse any number, as the header says.

Comment thread src/v/kafka/server/handlers/client_quotas.cc Outdated
@IoannisRP IoannisRP force-pushed the CORE-15271/user-based-quotas-kafka-api branch from af744d8 to ebdb04b Compare January 28, 2026 17:16
@IoannisRP

Copy link
Copy Markdown
Contributor Author

changes in force-push:

  • rebase to dev

@michael-redpanda michael-redpanda left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks good to me

Comment on lines +378 to +379
//} else if (component.entity_type == "ip") {
// return ip_predicate;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

leave in? Remove?

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.

these are the "guides" on where to extend when/if we want to support ip-based quotas.

They were already there from previous work and they were quite useful while adding user. So i still kept the ip ones for future reference.

@michael-redpanda michael-redpanda left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks fine minus gellert's comments

For backwards compatibility, a feature flag is added to protect against
using the user quota entity while the cluster during upgrade
@IoannisRP IoannisRP force-pushed the CORE-15271/user-based-quotas-kafka-api branch from ebdb04b to 49ccca4 Compare January 28, 2026 17:48
@IoannisRP

Copy link
Copy Markdown
Contributor Author

changes in force-push:

  • add missing "zero-size" check
  • remove feature flag check from describe

{
"Entity": [
{
"Type": "user",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe for a separate PR, but as we discussed earlier, let's ensure that we also have an upgrade test that ensures that the serde changes work end-to-end (create some quotas on an old cluster, upgrade one node, create some old quotas mid-upgrade, upgrade fully, verify everything works).

@IoannisRP IoannisRP merged commit aaec6a9 into redpanda-data:dev Jan 29, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants