-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add rate-limiting to ksql command topic #8809
Changes from 4 commits
b26d7b9
fba9fe4
67b1011
5b543be
6d168be
e2465ba
3aedfb1
181d762
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,10 +16,12 @@ | |
package io.confluent.ksql.rest.server.computation; | ||
|
||
import com.google.common.annotations.VisibleForTesting; | ||
import com.google.common.util.concurrent.RateLimiter; | ||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
import io.confluent.ksql.rest.Errors; | ||
import io.confluent.ksql.rest.entity.ClusterTerminateRequest; | ||
import io.confluent.ksql.rest.server.resources.IncompatibleKsqlCommandVersionException; | ||
import io.confluent.ksql.rest.server.resources.KsqlRestException; | ||
import io.confluent.ksql.rest.server.state.ServerState; | ||
import io.confluent.ksql.rest.util.ClusterTerminator; | ||
import io.confluent.ksql.rest.util.PersistentQueryCleanupImpl; | ||
|
@@ -90,6 +92,7 @@ public class CommandRunner implements Closeable { | |
private final Supplier<Boolean> commandTopicExists; | ||
private boolean commandTopicDeleted; | ||
private Status state = new Status(CommandRunnerStatus.RUNNING, CommandRunnerDegradedReason.NONE); | ||
private RateLimiter rateLimiter; | ||
|
||
public enum CommandRunnerStatus { | ||
RUNNING, | ||
|
@@ -148,7 +151,8 @@ public CommandRunner( | |
final Errors errorHandler, | ||
final KafkaTopicClient kafkaTopicClient, | ||
final String commandTopicName, | ||
final Metrics metrics | ||
final Metrics metrics, | ||
final double rateLimit | ||
) { | ||
this( | ||
statementExecutor, | ||
|
@@ -169,7 +173,8 @@ public CommandRunner( | |
commandDeserializer, | ||
errorHandler, | ||
() -> kafkaTopicClient.isTopicExists(commandTopicName), | ||
metrics | ||
metrics, | ||
rateLimit | ||
); | ||
} | ||
|
||
|
@@ -191,7 +196,8 @@ public CommandRunner( | |
final Deserializer<Command> commandDeserializer, | ||
final Errors errorHandler, | ||
final Supplier<Boolean> commandTopicExists, | ||
final Metrics metrics | ||
final Metrics metrics, | ||
final double rateLimit | ||
) { | ||
// CHECKSTYLE_RULES.ON: ParameterNumberCheck | ||
this.statementExecutor = Objects.requireNonNull(statementExecutor, "statementExecutor"); | ||
|
@@ -218,6 +224,7 @@ public CommandRunner( | |
Objects.requireNonNull(commandTopicExists, "commandTopicExists"); | ||
this.incompatibleCommandDetected = false; | ||
this.commandTopicDeleted = false; | ||
this.rateLimiter = RateLimiter.create(rateLimit); | ||
} | ||
|
||
/** | ||
|
@@ -346,6 +353,12 @@ private void executeStatement(final QueuedCommand queuedCommand) { | |
if (closed) { | ||
LOG.info("Execution aborted as system is closing down"); | ||
} else { | ||
if (!rateLimiter.tryAcquire()) { | ||
throw new KsqlRestException( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How will this error get handled? And can you a unit test to make sure its gets thrown and handled? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah writing a unit test rn - it will return an error back up to the user |
||
Errors.tooManyRequests( | ||
"Too many requests to the command topic within a 1 second timeframe" | ||
)); | ||
} | ||
statementExecutor.handleStatement(queuedCommand); | ||
LOG.info("Executed statement: " + commandStatement); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Do we need a double or can we use an int?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the library takes a double as
permitsPerSecond
https://guava.dev/releases/19.0/api/docs/index.html?com/google/common/util/concurrent/RateLimiter.htmlThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we set the default much higher (like 1000)? I think 10 is too low for a default and many users may inadvertently hit this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, could you move the config to KsqlRestConfig and follow a similar naming pattern to
KSQL_COMMAND_RUNNER_BLOCKED_THRESHHOLD_ERROR_MS
andDISTRIBUTED_COMMAND_RESPONSE_TIMEOUT_MS_CONFIG
withksql.server.command
prefix? I think we should keep the command topic/command runner configs together and named consistentlyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stevenpyzhang I moved it but now it can't be included in the
ImmutableProperties
list and we get issues when creating the rate limiter. I upped the limit for now, @rodesai WDYT about doing double max value versus 10? seems ok to meThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we should definitely disable rate-limiting by default by setting this to MAX_INT. We can enable in cloud as needed.