mirror of
https://github.com/mailscope/kumomta.git
synced 2026-09-07 19:18:57 +00:00
There's big explanation about this in the included docs change. The TL;DR is: we can now optionally queue the request and generate it asynchronously wrt. the incoming injection request. There's also a throttle that can be used to constrain this, because the asynchronous submission disconnects the injection request from any kind of back pressure that we'd otherwise have to indicate to the injector that there might be load related issues.
135 lines
4.8 KiB
Markdown
135 lines
4.8 KiB
Markdown
# `kumo.make_throttle(NAME, SPEC)`
|
|
|
|
{{since('2024.06.10-84e84b89')}}
|
|
|
|
Constructs and returns a named throttle object. A throttle allows constraining
|
|
the rate at which an operation can occur, according to a Generic Cell Rate
|
|
Algorithm.
|
|
|
|
When used together with
|
|
[kumo.configure.redis_throttles()](configure_redis_throttles.md), multiple
|
|
nodes can contribute to and respect a limit configured across a cluster.
|
|
|
|
The *name* parameter is an arbitrary name that can be used to define the
|
|
purpose and scope of a throttle. For example, you might define the purpose as
|
|
`throttle-ready-queue` and the scope to be a particular tenant. In that case
|
|
you might generate a name like `throttle-ready-queue-TENANT_NAME`. Multiple
|
|
throttle objects with the same name will increment and check the same underlying
|
|
throttle; the *name* parameter defines the throttle.
|
|
|
|
The *spec* parameter defines the permitted rate of the throttle, and has the
|
|
form `quantity/period` where quantity is a number and period can be a measure
|
|
of time.
|
|
|
|
Examples of throttles:
|
|
|
|
```
|
|
"10/s" -- 10 per second
|
|
"10/sec" -- 10 per second
|
|
"10/second" -- 10 per second
|
|
|
|
"50/m" -- 50 per minute
|
|
"50/min" -- 50 per minute
|
|
"50/minute" -- 50 per minute
|
|
|
|
"1,000/hr" -- 1000 per hour
|
|
"1_000/h" -- 1000 per hour
|
|
"1000/hour" -- 1000 per hour
|
|
|
|
"10_000/d" -- 10,000 per day
|
|
"10,000/day" -- 10,000 per day
|
|
```
|
|
|
|
{{since('dev', indent=True)}}
|
|
You can use the literal prefix `"local:"` to explicitly use a
|
|
node-local throttle, even when you have enabled redis throttles,
|
|
so `"local:10,000/s"` defines a `10,000/s` throttle that is node-local
|
|
and that doesn't use redis to share the throttle state with other nodes.
|
|
|
|
The returned throttle object has the following methods:
|
|
|
|
## throttle:sleep_if_throttled()
|
|
|
|
Checks to see if an operation can proceed, incrementing and sleeping the
|
|
current action until the operation can proceed.
|
|
|
|
Returns a boolean value to indicate whether the action was throttled; if it
|
|
returns `true` then it was throttled and a delay was applied.
|
|
|
|
This is useful for example when throttling the reception rate. In the example
|
|
below, the incoming SMTP session is paused during `MAIL FROM` until the message
|
|
is permitted by two sets of throttles, and then allowed to continue:
|
|
|
|
```lua
|
|
kumo.on('smtp_server_mail_from', function(sender)
|
|
-- Limit reception rate to 50/minute per unique sender
|
|
local throttle = kumo.make_throttle(
|
|
string.format('reception-rate-for-%s', sender),
|
|
'50/minute'
|
|
)
|
|
throttle:sleep_if_throttled()
|
|
|
|
-- Additionally, limit reception rate to 100/minute, regardless of the sender
|
|
local throttle = kumo.make_throttle('reception-rate', '100/minute')
|
|
throttle:sleep_if_throttled()
|
|
end)
|
|
```
|
|
|
|
## throttle:delay_message_if_throttled(msg)
|
|
|
|
This method is intended to be used in the
|
|
[throttle_insert_ready_queue](../events/throttle_insert_ready_queue.md) event.
|
|
|
|
It will evaluate the throttle, and if a delay is required, update the due
|
|
time on the message to reflect that.
|
|
|
|
```lua
|
|
kumo.on('throttle_insert_ready_queue', function(msg)
|
|
-- limit each tenant to 1000/hr
|
|
local tenant = msg:get_meta 'tenant'
|
|
local throttle = kumo.make_throttle(
|
|
string.format('tenant-send-limit-%s', tenant),
|
|
'1000/hr'
|
|
)
|
|
throttle:delay_message_if_throttled(msg)
|
|
end)
|
|
```
|
|
|
|
## throttle:throttle()
|
|
|
|
Checks to see if an operation can proceed, and increments the count if it is permitted.
|
|
The returned value indicates the outcome and returns a table with the following fields:
|
|
|
|
* `throttled` - a boolean that indicates whether the operation was throttled or
|
|
allowed. If `true`, the operation was throttled and should not be permitted
|
|
to proceed.
|
|
* `limit` - The total limit of this particular named throttle. Equivalent to the
|
|
`X-RateLimit-Limit` HTTP header that might be returned in various web services
|
|
that implement throttling.
|
|
* `remaining` - the remaining limit of this particular named throttle. Equivalent to the
|
|
`X-RateLimit-Remaining` HTTP header that might be returned in various web services
|
|
that implement throttling.
|
|
* `reset_after` - the remaining duration until the limit will reset to its maximum capacity.
|
|
Equivalent to the `X-RateLimit-Reset` HTTP that might be returned in various web
|
|
services that implement throttling.
|
|
* `retry_after` - the time until the operation should be retried, or `nil` if
|
|
the action was allowed.
|
|
|
|
This can be used to implement alternative strategies for the throttle delay.
|
|
For example, if you want to issue a generic transient failure when the limit
|
|
is exceeded you might do something like the following:
|
|
|
|
```lua
|
|
kumo.on('smtp_server_mail_from', function(sender)
|
|
-- Limit reception rate to 50/minute per unique sender
|
|
local throttle = kumo.make_throttle(
|
|
string.format('reception-rate-for-%s', sender),
|
|
'50/minute'
|
|
)
|
|
local result = throttle:throttle()
|
|
if result.throttled then
|
|
kumo.reject(451, '4.4.5 try again later')
|
|
end
|
|
end)
|
|
```
|