Skip to content

Commit

Permalink
Adding rate limit docs
Browse files Browse the repository at this point in the history
  • Loading branch information
edeandrea authored Apr 29, 2024
1 parent ca27e0c commit 86092bf
Showing 1 changed file with 71 additions and 1 deletion.
72 changes: 71 additions & 1 deletion docs/src/main/asciidoc/smallrye-fault-tolerance.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ implementation of the https://github.com/eclipse/microprofile-fault-tolerance/[M
specification.

In this guide, we demonstrate usage of MicroProfile Fault Tolerance annotations such as `@Timeout`, `@Fallback`,
`@Retry` and `@CircuitBreaker`.
`@Retry`, `@CircuitBreaker`, and `@RateLimit`.

== Prerequisites

Expand Down Expand Up @@ -438,6 +438,76 @@ To test this out, do the following:
5. Give it 5 seconds during which circuit breaker should close, and you should be able to make two successful requests
again.

== Adding Resiliency: Rate Limits

IMPORTANT: This is an additional feature of https://github.com/smallrye/smallrye-fault-tolerance/[SmallRye Fault Tolerance] and is not specified by MicroProfile Fault Tolerance.

It is possible to prevent an operation from being executed too often using a _rate limit_. Rate limits enforce a maximum number of permitted invocations in a time window of some length. For example, with a rate limit, one can make sure that a method may only be called 50 times per minute. Invocations that would exceed the limit are rejected with an exception of type `RateLimitException`.

Additionally, it is possible to define minimum spacing between invocations. For example, with a minimum spacing of 1 second, if a second invocation happens 500 millis after the first, it is rejected even if the limit would not be exceeded yet.

Rate limit is superficially similar to a bulkhead (concurrency limit), but is in fact quite different. Bulkhead limits the number of executions happening concurrently at any point in time. Rate limit limits the number of executions in a time window of some length, without considering concurrency.

Rate limits need to maintain some state between invocations: the number of recent invocations, the time stamp of last invocation, and so on. This state is a singleton, irrespective of the lifecycle of the bean that uses the `@RateLimit` annotation.

More specifically, the rate limit state is uniquely identified by the combination of the bean class (`java.lang.Class`) and the method object (`java.lang.reflect.Method`) representing the guarded method.

Let the Quarkus development mode run and in your IDE add the `@RateLimit` annotation to the `CoffeeResource#coffees()` method as follows and save the file:

[source,java]
----
import java.time.temporal.ChronoUnit;
import io.smallrye.faulttolerance.api.RateLimit;
...
public class CoffeeResource {
...
@GET
@RateLimit(value = 2, window = 10, windowUnit = ChronoUnit.SECONDS)
public List<Coffee> coffees() {
...
}
...
}
----

Hit refresh in your browser. The Quarkus development server will automatically detect the changes and recompile the app for you, so there’s no need to restart it.

You can hit refresh a couple more times. After 2 requests within a 10 second interval you should start seeing errors in the logs, similar to these:

[source]
----
INFO [org.acm.mic.fau.CoffeeResource] (executor-thread-1) CoffeeResource#coffees() invocation #1 returning successfully
ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /coffee failed, error id: d3e59090-fd45-4c67-844e-80a8f7fa6ee0-4: io.smallrye.faulttolerance.api.RateLimitException: org.acme.microprofile.faulttolerance.CoffeeResource#coffees rate limit exceeded
at io.smallrye.faulttolerance.core.rate.limit.RateLimit.doApply(RateLimit.java:58)
at io.smallrye.faulttolerance.core.rate.limit.RateLimit.apply(RateLimit.java:44)
at io.smallrye.faulttolerance.FaultToleranceInterceptor.syncFlow(FaultToleranceInterceptor.java:255)
at io.smallrye.faulttolerance.FaultToleranceInterceptor.intercept(FaultToleranceInterceptor.java:182)
at io.smallrye.faulttolerance.FaultToleranceInterceptor_Bean.intercept(Unknown Source)
at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:42)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:30)
at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:27)
at org.acme.microprofile.faulttolerance.CoffeeResource_Subclass.coffees(Unknown Source)
at org.acme.microprofile.faulttolerance.CoffeeResource$quarkusrestinvoker$coffees_73d7590ab944adfa130e4ad57c30282f825b2d18.invoke(Unknown Source)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:599)
at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2516)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2495)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1521)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:1583)
----

If `@Fallback` is used with `@RateLimit`, the fallback method or handler may be invoked if a `RateLimitException` is thrown, depending on the fallback configuration.

If `@Retry` is used with `@RateLimit`, each retry attempt is processed by the rate limit as an independent invocation. If `RateLimitException` is thrown, the execution may be retried, depending on how the retry is configured.

If `@CircuitBreaker` is used with `@RateLimit`, the circuit breaker is checked before enforcing the rate limit. If rate limiting results in `RateLimitException`, this may be counted as a failure, depending on how the circuit breaker is configured.

== Runtime configuration

You can override the annotations parameters at runtime inside your `application.properties` file.
Expand Down

0 comments on commit 86092bf

Please sign in to comment.