ratelimit: new per descriptor hits-addend support and dynamic hits addend#37567
Conversation
|
CC @envoyproxy/api-shepherds: Your approval is needed for changes made to |
| // For example, the ``%BYTES_RECEIVED%`` format string will be replaced with the number of bytes | ||
| // received in the request. | ||
| // | ||
| // Only one of the ``number`` or ``format`` fields can be set. |
There was a problem hiding this comment.
what will happen if both are set?
There was a problem hiding this comment.
This is oneof semantics. (although oneof is not recommend according to our style).
If both are set, the configuration will be rejected.
| // Optional hits_addend for the rate limit descriptor. If set the value will override the | ||
| // request level hits_addend. | ||
| // [#not-implemented-hide:] | ||
| google.protobuf.UInt32Value hits_addend = 3; |
There was a problem hiding this comment.
In the request level, it can be an integer or a format string. How come here it is only an integer?
There was a problem hiding this comment.
The format string is used to extract a number based on the request and stream info. See the comments of format string comment
// Substitution format string to extract the number of hits to add to the rate limit descriptor.
// The same :ref:`format specifier <config_access_log_format>` as used for
// :ref:`HTTP access logging <config_access_log>` applies here.
//
// .. note::
// The format string must contain only single valid substitution field that will be replaced
// with a non-negative number.
//
// For example, the ``%BYTES_RECEIVED%`` format string will be replaced with the number of bytes
// received in the request.
SGTM. |
|
cc @abeyad I complete the local ratelimit version of the per descriptor custom hits addend support. When the request is coming, we will generated a list of descriptors based on the configuration. If the When a descriptor matchs a rule, in the previous implementation, the fixed value 1 will be used as hits addend. Now, in the new implementation, the custom |
envoy/ratelimit/ratelimit.h
Outdated
| /** | ||
| * A single rate limit request descriptor. See ratelimit.proto. | ||
| * This is generated from the request based on the configured rate limit actions. | ||
| */ | ||
| struct Descriptor : public DescriptorBase { | ||
| absl::optional<RateLimitOverride> limit_ = absl::nullopt; | ||
| absl::optional<uint32_t> hits_addend_ = absl::nullopt; | ||
| }; | ||
|
|
||
| using LocalDescriptor = DescriptorBase; |
There was a problem hiding this comment.
In the previous implementation, the class LocalDescriptor has two usages:
- as the key of rate limit rules that every descriptor is related to a token bucket.
- as the output of the descriptor populating of local rate limt filter. It will be used to find a matched rate limit rule.
But now, to support per descriptor hits_addend, we need a new class to represent the output of the descriptor populating. Finally, we choose to enhance and re-use the Descriptor class. (The Descriptor class is used as the output of global rate limit filter. Re-using it could also simplify future development when we want to add similar support for global rate limit filter in the future.)
2e2b2c3 to
dc93699
Compare
|
/retest |
| // :ref:`Route.typed_per_filter_config<envoy_v3_api_field_config.route.v3.Route.typed_per_filter_config>`, etc. | ||
| Override limit = 4; | ||
|
|
||
| // An optional hits addend to be appended to the descriptor produced by this rate limit |
There was a problem hiding this comment.
instead of appending to the descriptor, it should be used to populate the hits_addend field in the RLS request
https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ratelimit/v3/rls.proto#service-ratelimit-v3-ratelimitrequest
There was a problem hiding this comment.
Single RateLimit message is used to generate a single descriptor. The RateLimit.hits_addend field here is also a descriptor level configuration. So, the generated value of RateLimit.hits_addend field here should also be populated to the per-descriptor Descriptor.hits_addend rather than request level RateLimitRequest.hits_addend.
There was a problem hiding this comment.
shouldnt this be one level up, under https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-ratelimit ? so it can set the hits_addend field of https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ratelimit/v3/rls.proto#service-ratelimit-v3-ratelimitrequest ?
The ratelimit request holds a list of descriptors
There was a problem hiding this comment.
The https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-ratelimit is what I said single RateLimit.
Every route/vhost will have a list of RateLimit configuration. Every Ratelimit will generate a single descriptor (a single descriptor is composited of multiple descriptor entries). All these descriptors finally composite the rate limit request.
But note, every descriptor is evaluated independently and will match to completely different rules. One of the most important target of this PR is to support per-descriptor hits addend, then we can limit different resource like qps or bandwidth in same request. You can check this PR's description for the PR's target. I have no plan to enhance the request level hits addend because it could be replaced by descriptor-level hits addend.
|
Hi, @mattklein123 , could you take a look when you get some free time? Thansk. This PR only contain local-ratelimit related change. And I will create a new PR to support global rate limit after this is done. |
Signed-off-by: wangbaiping(wbpcode) <wangbaiping@bytedance.com>
18489a3 to
774066d
Compare
|
Finally, reduced 50% code changes. I have tried my best to reduce the complexity of review. orz. |
…er-descriptor-hits-adden-support
| // Substitution format string to extract the number of hits to add to the rate limit descriptor. | ||
| // The same :ref:`format specifier <config_access_log_format>` as used for | ||
| // :ref:`HTTP access logging <config_access_log>` applies here. | ||
| // | ||
| // .. note:: | ||
| // The format string must contain only single valid substitution field that will be replaced | ||
| // with a non-negative number. | ||
| // | ||
| // For example, the ``%BYTES_RECEIVED%`` format string will be replaced with the number of bytes | ||
| // received in the request. | ||
| // | ||
| // One of the ``number`` or ``format`` fields should be set but not both. | ||
| string format = 2 [(validate.rules).string = {prefix: "%" suffix: "%" ignore_empty: true}]; |
There was a problem hiding this comment.
What is the behavior if this is invalid and/or is a format string that doesn't lead to an integer? Please clarify.
| do { | ||
| // expected_tokens is either initialized above or reloaded during the CAS failure below. | ||
| if (expected_tokens == 0) { | ||
| if (expected_tokens < to_consume) { |
There was a problem hiding this comment.
I didn't look at this carefully but did you audit everything to make sure there can be no underflow or overflow conditions?
There was a problem hiding this comment.
I didn't look at this carefully but did you audit everything to make sure there can be no underflow or overflow conditions?
Good point. I am sure underflow or overflow won't happen because we only consum the token when the expected_tokens larges then to_consume. And they both are uint32_t.
But I think this notices me to add a more strict check to the value range of the to_consume.
Use a uint64_t directly would much simple. And the max to_consume is limited to 1000000000. If even the uint64_t is enough in this case, them just let it explode.
| if (hits_addend.has_number_value()) { | ||
| descriptor.hits_addend_ = static_cast<uint32_t>(hits_addend.number_value()); | ||
| } else { | ||
| ENVOY_LOG(warn, "hits_addend must be a number"); |
There was a problem hiding this comment.
This can heavily log spam if there is a config mistake. This should be a warn every of some type. Optimally we would catch as much as possible at config load time which isn't happening.
There was a problem hiding this comment.
This can heavily log spam if there is a config mistake. This should be a warn every of some type. Optimally we would catch as much as possible at config load time which isn't happening.
Currently we have no way to check the provider's expected return type. I can change the level to debug and add more clear descripription on the API.
Signed-off-by: wangbaiping(wbpcode) <wangbaiping@bytedance.com>
|
…er-descriptor-hits-adden-support
| if (success) { | ||
| descriptor.hits_addend_ = static_cast<uint64_t>(hits_addend); | ||
| } else { | ||
| ENVOY_LOG(debug, "Invalid hits_addend: {}", hits_addend_value.DebugString()); |
There was a problem hiding this comment.
I think this is going to be very confusing to debug and I would go back to WARN but use one of the power of 2 or timed variants for that, but up to you.
There was a problem hiding this comment.
I think this is going to be very confusing to debug and I would go back to WARN but use one of the power of 2 or timed variants for that, but up to you.
Will change this in next PR. There is a series of works.
|
/retest |
|
/retest |
…er-descriptor-hits-adden-support
|
/retest |
Commit Message: api: new per descriptor hits-addend support and dynamic hits addend
Additional Description:
Now, we could get custom hits_addend from the
envoy.ratelimit.hits_addend. But if there are multiple rate limit filters that requrie custom hits_addend, theenvoy.ratelimit.hits_addendcouldn't meet the requirement.And we cann't also to support different hits_addend for diffferent descriptots in same request.
This API changes try to meet above two requirements.
Risk Level: low.
Testing: n/a.
Docs Changes: n/a.
Release Notes: n/a.
Platform Specific Features: n/a.