Skip to content

Commit 973ed33

Browse files
committed
KAFKA-13022: Optimize ClientQuotasImage#describe
Signed-off-by: PoAn Yang <[email protected]>
1 parent 101e15b commit 973ed33

File tree

2 files changed

+140
-27
lines changed

2 files changed

+140
-27
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.kafka.jmh.metadata;
18+
19+
import org.apache.kafka.common.message.DescribeClientQuotasRequestData;
20+
import org.apache.kafka.common.quota.ClientQuotaEntity;
21+
import org.apache.kafka.common.requests.DescribeClientQuotasRequest;
22+
import org.apache.kafka.image.ClientQuotaImage;
23+
import org.apache.kafka.image.ClientQuotasImage;
24+
import org.apache.kafka.server.config.QuotaConfig;
25+
26+
import org.openjdk.jmh.annotations.Benchmark;
27+
import org.openjdk.jmh.annotations.BenchmarkMode;
28+
import org.openjdk.jmh.annotations.Fork;
29+
import org.openjdk.jmh.annotations.Level;
30+
import org.openjdk.jmh.annotations.Measurement;
31+
import org.openjdk.jmh.annotations.Mode;
32+
import org.openjdk.jmh.annotations.OutputTimeUnit;
33+
import org.openjdk.jmh.annotations.Param;
34+
import org.openjdk.jmh.annotations.Scope;
35+
import org.openjdk.jmh.annotations.Setup;
36+
import org.openjdk.jmh.annotations.State;
37+
import org.openjdk.jmh.annotations.Warmup;
38+
39+
import java.util.HashMap;
40+
import java.util.List;
41+
import java.util.Map;
42+
import java.util.concurrent.TimeUnit;
43+
44+
@State(Scope.Benchmark)
45+
@Fork(value = 1)
46+
@Warmup(iterations = 5)
47+
@Measurement(iterations = 15)
48+
@BenchmarkMode(Mode.AverageTime)
49+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
50+
public class ClientQuotasImageDescribeBenchmark {
51+
52+
@Param({"10", "100", "1000"})
53+
private int entityCount;
54+
55+
private ClientQuotasImage clientQuotasImage;
56+
57+
@Setup(Level.Trial)
58+
public void setup() {
59+
clientQuotasImage = createClientQuotasImage(entityCount);
60+
}
61+
62+
static ClientQuotasImage createClientQuotasImage(int entityCount) {
63+
Map<ClientQuotaEntity, ClientQuotaImage> entities = new HashMap<>();
64+
for (int i = 0; i < entityCount; i++) {
65+
ClientQuotaEntity entity = new ClientQuotaEntity(Map.of(ClientQuotaEntity.USER, "user-" + i));
66+
entities.put(entity, new ClientQuotaImage(Map.of(QuotaConfig.REQUEST_PERCENTAGE_OVERRIDE_CONFIG, 1.0)));
67+
}
68+
return new ClientQuotasImage(entities);
69+
}
70+
71+
@Benchmark
72+
public void describeSpecified() {
73+
clientQuotasImage.describe(new DescribeClientQuotasRequestData()
74+
.setComponents(List.of(new DescribeClientQuotasRequestData.ComponentData()
75+
.setEntityType(ClientQuotaEntity.USER)
76+
.setMatchType(DescribeClientQuotasRequest.MATCH_TYPE_SPECIFIED)
77+
.setMatch(null))));
78+
}
79+
80+
@Benchmark
81+
public void describeDefault() {
82+
clientQuotasImage.describe(new DescribeClientQuotasRequestData()
83+
.setComponents(List.of(new DescribeClientQuotasRequestData.ComponentData()
84+
.setEntityType(ClientQuotaEntity.USER)
85+
.setMatchType(DescribeClientQuotasRequest.MATCH_TYPE_DEFAULT)
86+
.setMatch(null))));
87+
}
88+
89+
@Benchmark
90+
public void describeExact() {
91+
clientQuotasImage.describe(new DescribeClientQuotasRequestData()
92+
.setComponents(List.of(new DescribeClientQuotasRequestData.ComponentData()
93+
.setEntityType(ClientQuotaEntity.USER)
94+
.setMatchType(DescribeClientQuotasRequest.MATCH_TYPE_EXACT)
95+
.setMatch("user-0"))));
96+
}
97+
}

metadata/src/main/java/org/apache/kafka/image/ClientQuotasImage.java

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,20 @@ public final class ClientQuotasImage {
5353
public static final ClientQuotasImage EMPTY = new ClientQuotasImage(Collections.emptyMap());
5454

5555
private final Map<ClientQuotaEntity, ClientQuotaImage> entities;
56+
private final Map<String, Map<String, Set<Entry<ClientQuotaEntity, ClientQuotaImage>>>> entitiesByType;
5657

5758
public ClientQuotasImage(Map<ClientQuotaEntity, ClientQuotaImage> entities) {
5859
this.entities = Collections.unmodifiableMap(entities);
60+
Map<String, Map<String, Set<Entry<ClientQuotaEntity, ClientQuotaImage>>>> entitiesByType = new HashMap<>();
61+
for (Entry<ClientQuotaEntity, ClientQuotaImage> entry : entities.entrySet()) {
62+
ClientQuotaEntity entity = entry.getKey();
63+
for (Entry<String, String> entityEntry : entity.entries().entrySet()) {
64+
entitiesByType.putIfAbsent(entityEntry.getKey(), new HashMap<>());
65+
entitiesByType.get(entityEntry.getKey()).putIfAbsent(entityEntry.getValue(), new HashSet<>());
66+
entitiesByType.get(entityEntry.getKey()).get(entityEntry.getValue()).add(entry);
67+
}
68+
}
69+
this.entitiesByType = Collections.unmodifiableMap(entitiesByType);
5970
}
6071

6172
public boolean isEmpty() {
@@ -126,40 +137,45 @@ public DescribeClientQuotasResponseData describe(DescribeClientQuotasRequestData
126137
"user or clientId filter component.");
127138
}
128139
}
129-
// TODO: this is O(N). We should add indexing here to speed it up. See KAFKA-13022.
130-
for (Entry<ClientQuotaEntity, ClientQuotaImage> entry : entities.entrySet()) {
131-
ClientQuotaEntity entity = entry.getKey();
132-
ClientQuotaImage quotaImage = entry.getValue();
133-
if (matches(entity, exactMatch, typeMatch, request.strict())) {
134-
response.entries().add(toDescribeEntry(entity, quotaImage));
135-
}
136-
}
137-
return response;
138-
}
139140

140-
private static boolean matches(ClientQuotaEntity entity,
141-
Map<String, String> exactMatch,
142-
Set<String> typeMatch,
143-
boolean strict) {
144-
if (strict) {
145-
if (entity.entries().size() != exactMatch.size() + typeMatch.size()) {
146-
return false;
141+
Set<ClientQuotaEntity> addedEntities = new HashSet<>();
142+
for (Entry<String, String> exactMatchEntry : exactMatch.entrySet()) {
143+
if (entitiesByType.containsKey(exactMatchEntry.getKey()) &&
144+
entitiesByType.get(exactMatchEntry.getKey()).containsKey(exactMatchEntry.getValue())) {
145+
for (Entry<ClientQuotaEntity, ClientQuotaImage> entry : entitiesByType.get(exactMatchEntry.getKey()).get(exactMatchEntry.getValue())) {
146+
if (request.strict() && !entry.getKey().entries().equals(exactMatch)) {
147+
continue;
148+
}
149+
if (!addedEntities.contains(entry.getKey())) {
150+
addedEntities.add(entry.getKey());
151+
response.entries().add(toDescribeEntry(entry.getKey(), entry.getValue()));
152+
}
153+
}
147154
}
148155
}
149-
for (Entry<String, String> entry : exactMatch.entrySet()) {
150-
if (!entity.entries().containsKey(entry.getKey())) {
151-
return false;
152-
}
153-
if (!Objects.equals(entity.entries().get(entry.getKey()), entry.getValue())) {
154-
return false;
156+
157+
for (String type : typeMatch) {
158+
if (entitiesByType.containsKey(type)) {
159+
for (Set<Entry<ClientQuotaEntity, ClientQuotaImage>> entrySet : entitiesByType.get(type).values()) {
160+
for (Entry<ClientQuotaEntity, ClientQuotaImage> entry : entrySet) {
161+
if (request.strict() && entry.getKey().entries().size() != typeMatch.size()) {
162+
continue;
163+
}
164+
if (!addedEntities.contains(entry.getKey())) {
165+
addedEntities.add(entry.getKey());
166+
response.entries().add(toDescribeEntry(entry.getKey(), entry.getValue()));
167+
}
168+
}
169+
}
155170
}
156171
}
157-
for (String type : typeMatch) {
158-
if (!entity.entries().containsKey(type)) {
159-
return false;
172+
173+
if (!request.strict() && exactMatch.isEmpty() && typeMatch.isEmpty()) {
174+
for (Entry<ClientQuotaEntity, ClientQuotaImage> entry : entities.entrySet()) {
175+
response.entries().add(toDescribeEntry(entry.getKey(), entry.getValue()));
160176
}
161177
}
162-
return true;
178+
return response;
163179
}
164180

165181
private static EntryData toDescribeEntry(ClientQuotaEntity entity,

0 commit comments

Comments
 (0)