Skip to content

ratelimit: new per descriptor hits-addend support and dynamic hits addend#37567

Merged
wbpcode merged 5 commits intoenvoyproxy:mainfrom
wbpcode:dev-per-descriptor-hits-adden-support
Dec 18, 2024
Merged

ratelimit: new per descriptor hits-addend support and dynamic hits addend#37567
wbpcode merged 5 commits intoenvoyproxy:mainfrom
wbpcode:dev-per-descriptor-hits-adden-support

Conversation

@wbpcode
Copy link
Copy Markdown
Member

@wbpcode wbpcode commented Dec 9, 2024

Commit Message: api: new per descriptor hits-addend support and dynamic hits addend
Additional Description:

  1. 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, the envoy.ratelimit.hits_addend couldn't meet the requirement.

  2. 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.

@repokitteh-read-only
Copy link
Copy Markdown

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to (api/envoy/|docs/root/api-docs/).
envoyproxy/api-shepherds assignee is @abeyad
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #37567 was opened by wbpcode.

see: more, trace.

Copy link
Copy Markdown
Contributor

@abeyad abeyad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wbpcode should we add the implementation using this API to the PR so we can see how it'll be used?

// 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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what will happen if both are set?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the request level, it can be an integer or a format string. How come here it is only an integer?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@wbpcode
Copy link
Copy Markdown
Member Author

wbpcode commented Dec 10, 2024

@wbpcode should we add the implementation using this API to the PR so we can see how it'll be used?

SGTM.

@wbpcode wbpcode changed the title api: new per descriptor hits-addend support and dynamic hits addend ratelimit: new per descriptor hits-addend support and dynamic hits addend Dec 10, 2024
@wbpcode
Copy link
Copy Markdown
Member Author

wbpcode commented Dec 10, 2024

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 hits_addend is configured, the filter will also generate a dynamic hits_addend value based on the request info and configuration.

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 hits_addend that generated in the previous step will be used.

Comment on lines +78 to +87
/**
* 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;
Copy link
Copy Markdown
Member Author

@wbpcode wbpcode Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the previous implementation, the class LocalDescriptor has two usages:

  1. as the key of rate limit rules that every descriptor is related to a token bucket.
  2. 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.)

@wbpcode wbpcode force-pushed the dev-per-descriptor-hits-adden-support branch from 2e2b2c3 to dc93699 Compare December 10, 2024 16:36
@wbpcode
Copy link
Copy Markdown
Member Author

wbpcode commented Dec 10, 2024

/retest

abeyad
abeyad previously approved these changes Dec 10, 2024
Copy link
Copy Markdown
Contributor

@abeyad abeyad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm api

// :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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member Author

@wbpcode wbpcode Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

@wbpcode wbpcode Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@wbpcode
Copy link
Copy Markdown
Member Author

wbpcode commented Dec 11, 2024

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>
@wbpcode wbpcode force-pushed the dev-per-descriptor-hits-adden-support branch from 18489a3 to 774066d Compare December 11, 2024 16:32
@wbpcode
Copy link
Copy Markdown
Member Author

wbpcode commented Dec 11, 2024

Finally, reduced 50% code changes. I have tried my best to reduce the complexity of review. orz.

Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/wait

Comment on lines +2178 to +2190
// 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}];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't look at this carefully but did you audit everything to make sure there can be no underflow or overflow conditions?

Copy link
Copy Markdown
Member Author

@wbpcode wbpcode Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@wbpcode
Copy link
Copy Markdown
Member Author

wbpcode commented Dec 12, 2024

  1. added more comments about the behavior of hits_addend.
  2. added more tests for unexpected hits_addend.

if (success) {
descriptor.hits_addend_ = static_cast<uint64_t>(hits_addend);
} else {
ENVOY_LOG(debug, "Invalid hits_addend: {}", hits_addend_value.DebugString());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@wbpcode
Copy link
Copy Markdown
Member Author

wbpcode commented Dec 18, 2024

/retest

@wbpcode wbpcode enabled auto-merge (squash) December 18, 2024 03:47
@wbpcode
Copy link
Copy Markdown
Member Author

wbpcode commented Dec 18, 2024

/retest

@wbpcode
Copy link
Copy Markdown
Member Author

wbpcode commented Dec 18, 2024

/retest

@wbpcode wbpcode merged commit cac9b87 into envoyproxy:main Dec 18, 2024
@wbpcode wbpcode deleted the dev-per-descriptor-hits-adden-support branch December 30, 2024 10:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants