Rate Limiting Step Runs in Hatchet
Hatchet allows you to enforce rate limits on step runs in your workflows, enabling you to control the rate at which workflow runs consume resources, such as external API calls, database queries, or other services. By defining rate limits, you can prevent step runs from exceeding a certain number of requests per time window (e.g., per second, minute, or hour), ensuring efficient resource utilization and avoiding overloading external services.
The state of active rate limits can be viewed in the dashboard in the Rate Limit
resource tab.
Dynamic vs Static Rate Limits
Hatchet offers two patterns for Rate Limiting step runs:
- Dynamic Rate Limits: Allows for complex rate limiting scenarios, such as per-user limits, by using
input
oradditional_metadata
keys to upsert a limit at runtime. - Static Rate Limits: Allows for simple rate limiting for resources known prior to runtime (e.g., external APIs).
Dynamic Rate Limits
Dynamic rate limits are ideal for complex scenarios where rate limits need to be partitioned by resources that are only known at runtime.
This pattern is especially useful for:
- Rate limiting individual users or tenants
- Implementing variable rate limits based on subscription tiers or user roles
- Dynamically adjusting limits based on real-time system load or other factors
How It Works
- Define the dynamic rate limit key with a CEL (Common Expression Language) Expression on the key, referencing either
input
oradditional_metadata
. - Provide this key as part of the workflow trigger or event
input
oradditional_metadata
at runtime. - Hatchet will create or update the rate limit based on the provided key and enforce it for the step run.
Note: Dynamic keys are a shared resource, this means the same rendered cel on multiple steps will be treated as one global rate limit.
Declaring and Consuming Dynamic Rate Limits
Note:
dynamic_key
must be a CEL expression.units
andlimits
can be either an integer or a CEL expression.
@hatchet.step(rate_limits=[
RateLimit(
dynamic_key='input.user_id',
units=1,
limit=10,
duration=RateLimitDuration.MINUTE,
)])
def step1(self, context: Context):
print("executed step1")
pass
Static Rate Limits
Static Rate Limits (formerly known as Global Rate Limits) are defined as part of your worker startup lifecycle prior to runtime. This model provides a single "source of truth" for pre-defined resources such as:
- External API resources that have a rate limit across all users or tenants
- Database connection pools with a maximum number of concurrent connections
- Shared computing resources with limited capacity
How It Works
- Declare static rate limits using the
put_rate_limit
method in theAdmin
client before starting your worker. - Specify the units of consumption for a specific rate limit key in each step definition using the
rate_limits
configuration. - Hatchet enforces the defined rate limits by tracking the number of units consumed by each step run across all workflow runs.
If a step run exceeds the rate limit, Hatchet re-queues the step run until the rate limit is no longer exceeded.
Declaring Static Limits
Define the static rate limits that can be consumed by any step run across all workflow runs using the put_rate_limit
method in the Admin
client within your code.
hatchet.admin.put_rate_limit('example-limit', 10, RateLimitDuration.MINUTE)
Consuming Static Rate Limits
With your rate limit key defined, specify the units of consumption for a specific key in each step definition by adding the rate_limits
configuration to your step definition in your workflow.
@hatchet.step(rate_limits=[RateLimit(key='example-limit', units=1)])
def step1(self, context: Context):
print("executed step1")
pass
Limiting Workflow Runs
To rate limit an entire workflow run, it's recommended to specify the rate limit configuration on the entry step (i.e., the first step in the workflow). This will gate the execution of all downstream steps in the workflow.