Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): Add variable-cost token bucket rate limiting algorithm #4911

Merged
merged 219 commits into from
Dec 6, 2023

Conversation

rifont
Copy link
Collaborator

@rifont rifont commented Nov 28, 2023

What change does this PR introduce?

  • Adds a variable-cost token bucket rate limiting algorithm implemented in a Redis Lua script
    • Add benchmarking and throttle evaluation tests. These are skipped but can be used when making changes to the algorithm to verify expected performance is kept within bounds.
  • Adds tests for varying cost endpoints

Why was this change needed?

A variable cost rate limiting algorithm is needed to provide the capability for different API controllers and methods to have a varying cost. It also lays a foundation for dynamic costing of resource consumption.

Other information (Screenshots)

Bulk Endpoint E2E Tests
image

Redis Benchmark Tests
token-1
token-2
token-3

Test output in text form

``` Redis EVAL script benchmarks Low Load - 0% Throttled - Sustained Single Window Params: Total Req: 5,000 Users: 2,500 Throttled: 0% High Cost: 0% Jitter: 800ms Stats: Total Time: 804ms Avg: 0.8ms Stdev: 1.2 p(95): 3 Throttled: 0 Request Time (ms) 0.80: ************************************************** 1.60: ****************************** 2.40: ********* 3.20: *** 4.00: 4.80: * 5.60: * 6.40: 7.20: 8.00: Script Performance ✔ should be able to process 5,000 evaluations in less than 1000ms ✔ should have average evaluation duration less than 10ms ✔ should have 95th percentile evaluation duration less than 30ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests Medium Load - 0% Throttled - Sustained Single Window Params: Total Req: 10,000 Users: 5,000 Throttled: 0% High Cost: 0% Jitter: 800ms Stats: Total Time: 799ms Avg: 1.5ms Stdev: 2.0 p(95): 6 Throttled: 0 Request Time (ms) 1.20: ************************************************** 2.40: ******* 3.60: ** 4.80: *** 6.00: * 7.20: * 8.40: 9.60: 10.80: 12.00: Script Performance ✔ should be able to process 10,000 evaluations in less than 1000ms ✔ should have average evaluation duration less than 20ms ✔ should have 95th percentile evaluation duration less than 50ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests High Load - 0% Throttled - Sustained Single Window Params: Total Req: 20,000 Users: 10,000 Throttled: 0% High Cost: 0% Jitter: 800ms Stats: Total Time: 799ms Avg: 7.4ms Stdev: 15.5 p(95): 53 Throttled: 0 Request Time (ms) 6.60: ************************************************** 13.20: * 19.80: * 26.40: * 33.00: * 39.60: 46.20: ** 52.80: 59.40: ** 66.00: Script Performance ✔ should be able to process 20,000 evaluations in less than 1000ms ✔ should have average evaluation duration less than 200ms ✔ should have 95th percentile evaluation duration less than 500ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests Extreme Load - 0% Throttled - Sustained Single Window Params: Total Req: 40,000 Users: 20,000 Throttled: 0% High Cost: 0% Jitter: 800ms Stats: Total Time: 799ms Avg: 46.6ms Stdev: 39.0 p(95): 107 Throttled: 0 Request Time (ms) 12.80: ************************************************** 25.60: ****************************** 38.40: ****** 51.20: ******* 64.00: ********** 76.80: ***** 89.60: ******************* 102.40: ********************* 115.20: ************** 128.00: * Script Performance ✔ should be able to process 40,000 evaluations in less than 2000ms ✔ should have average evaluation duration less than 500ms ✔ should have 95th percentile evaluation duration less than 2000ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests High Load - 0% Throttled - Burst Single Window Params: Total Req: 20,000 Users: 10,000 Throttled: 0% High Cost: 0% Jitter: 200ms Stats: Total Time: 582ms Avg: 274.3ms Stdev: 51.6 p(95): 322 Throttled: 0 Request Time (ms) 33.50: * 67.00: * 100.50: 134.00: * 167.50: 201.00: 234.50: ********* 268.00: *************************** 301.50: ************************************************** 335.00: **************************************** Script Performance ✔ should be able to process 20,000 evaluations in less than 1000ms ✔ should have average evaluation duration less than 500ms ✔ should have 95th percentile evaluation duration less than 1000ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests Extreme Load - 0% Throttled - Burst Single Window Params: Total Req: 40,000 Users: 20,000 Throttled: 0% High Cost: 0% Jitter: 200ms Stats: Total Time: 1,365ms Avg: 630.5ms Stdev: 176.8 p(95): 940 Throttled: 0 Request Time (ms) 98.00: * 196.00: 294.00: 392.00: 490.00: ************************ 588.00: ************* 686.00: ************************************************** 784.00: 882.00: ******** 980.00: *************** Script Performance ✔ should be able to process 40,000 evaluations in less than 3000ms ✔ should have average evaluation duration less than 1500ms ✔ should have 95th percentile evaluation duration less than 2000ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests High Load - 50% Throttled - Burst Single Window Params: Total Req: 20,000 Users: 10,000 Throttled: 50% High Cost: 0% Jitter: 200ms Stats: Total Time: 449ms Avg: 177.3ms Stdev: 46.7 p(95): 211 Throttled: 0 Request Time (ms) 21.90: ** 43.80: *** 65.70: ** 87.60: 109.50: * 131.40: ****** 153.30: 175.20: *********** 197.10: ************************************************** 219.00: ******************************************** Script Performance ✔ should be able to process 20,000 evaluations in less than 1000ms ✔ should have average evaluation duration less than 500ms ✔ should have 95th percentile evaluation duration less than 1000ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests High Load - 50% Throttled - Sustained Single Window Params: Total Req: 20,000 Users: 10,000 Throttled: 50% High Cost: 0% Jitter: 800ms Stats: Total Time: 800ms Avg: 4.7ms Stdev: 10.2 p(95): 33 Throttled: 0 Request Time (ms) 4.30: ************************************************** 8.60: * 12.90: 17.20: 21.50: 25.80: 30.10: * 34.40: * 38.70: * 43.00: * Script Performance ✔ should be able to process 20,000 evaluations in less than 1000ms ✔ should have average evaluation duration less than 500ms ✔ should have 95th percentile evaluation duration less than 500ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests High Load - 50% Throttled & 50% High-Cost - Sustained Multiple Windows Params: Total Req: 40,000 Users: 20,000 Throttled: 50% High Cost: 50% Jitter: 2200ms Stats: Total Time: 2,200ms Avg: 1.6ms Stdev: 3.2 p(95): 7 Throttled: 9,405 Request Time (ms) 2.70: ************************************************** 5.40: **** 8.10: * 10.80: * 13.50: 16.20: 18.90: 21.60: 24.30: 27.00: Script Performance ✔ should be able to process 40,000 evaluations in less than 3000ms ✔ should have average evaluation duration less than 30ms ✔ should have 95th percentile evaluation duration less than 100ms Script Throttle Evaluation ✔ should throttle between 8064 and 12096 requests Extreme Load - 50% Throttled & 50% High-Cost - Sustained Multiple Windows Params: Total Req: 80,000 Users: 40,000 Throttled: 50% High Cost: 50% Jitter: 2200ms Stats: Total Time: 2,320ms Avg: 13.6ms Stdev: 27.2 p(95): 79 Throttled: 18,671 Request Time (ms) 19.50: ************************************************** 39.00: ***** 58.50: *** 78.00: ** 97.50: * 117.00: 136.50: 156.00: 175.50: 195.00: Script Performance ✔ should be able to process 80,000 evaluations in less than 4000ms ✔ should have average evaluation duration less than 1000ms ✔ should have 95th percentile evaluation duration less than 1500ms Script Throttle Evaluation ✔ should throttle between 16130 and 24196 requests High Load - 50% Throttled & 90% High-Cost - Sustained Multiple Windows Params: Total Req: 40,000 Users: 20,000 Throttled: 50% High Cost: 90% Jitter: 2200ms Stats: Total Time: 2,194ms Avg: 1.1ms Stdev: 1.9 p(95): 4 Throttled: 14,582 Request Time (ms) 2.00: ************************************************** 4.00: ******* 6.00: * 8.00: 10.00: 12.00: 14.00: 16.00: 18.00: 20.00: Script Performance ✔ should be able to process 40,000 evaluations in less than 3000ms ✔ should have average evaluation duration less than 50ms ✔ should have 95th percentile evaluation duration less than 200ms Script Throttle Evaluation ✔ should throttle between 10754 and 16131 requests High Load - 50% Throttled & 0% Unique - Sustained Multiple Windows Params: Total Req: 40,000 Users: 1 Throttled: 50% High Cost: 0% Jitter: 2200ms Stats: Total Time: 2,200ms Avg: 0.9ms Stdev: 1.4 p(95): 4 Throttled: 0 Request Time (ms) 1.10: ************************************************** 2.20: *** 3.30: * 4.40: * 5.50: 6.60: 7.70: 8.80: 9.90: 11.00: Script Performance ✔ should be able to process 40,000 evaluations in less than 3000ms ✔ should have average evaluation duration less than 30ms ✔ should have 95th percentile evaluation duration less than 200ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests High Load - 50% Throttled & 100% Unique - Sustained Multiple Windows Params: Total Req: 40,000 Users: 40,000 Throttled: 50% High Cost: 0% Jitter: 2200ms Stats: Total Time: 2,210ms Avg: 1.8ms Stdev: 3.6 p(95): 7 Throttled: 0 Request Time (ms) 4.10: ************************************************** 8.20: ** 12.30: 16.40: 20.50: 24.60: 28.70: 32.80: 36.90: 41.00: Script Performance ✔ should be able to process 40,000 evaluations in less than 3000ms ✔ should have average evaluation duration less than 30ms ✔ should have 95th percentile evaluation duration less than 100ms Script Throttle Evaluation ✔ should throttle between 0 and 0 requests

[Nest] 23992 - 11/28/2023, 5:48:36 PM ERROR [InMemoryProviderService] Error: Connection is closed.
[Provider: Elasticache] In-memory provider service shutdown has failed

57 passing (27s)


</p>
</details> 

rifont added 30 commits November 3, 2023 13:36
Copy link
Contributor

@Cliftonz Cliftonz left a comment

Choose a reason for hiding this comment

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

The load testing needs to be moved out into its own k6 scripts in enterpise repo but other then that it looks good

@rifont
Copy link
Collaborator Author

rifont commented Dec 4, 2023

The load testing needs to be moved out into its own k6 scripts in enterpise repo but other then that it looks good

My intention here is for these Redis-only benchmark tests to be used if anyone (team or community) makes further changes to the algorithm, ensuring that the current baseline is still met. Without these tests being public, changes could be made to the algorithm without proper testing.

I still intend to perform E2E load testing via API requests in K6, as it's important for us to gauge how the system performs as a whole, especially given shared use of the In-Memory Redis cluster. The User/time variation strategy in these tests will be useful here, and probably quite similar. These tests will live in the enterprise repo.

@rifont rifont requested a review from Cliftonz December 4, 2023 23:22
@@ -35,9 +35,6 @@ export type RegionLimiter = ReturnType<typeof Ratelimit.tokenBucket>;
* rate.
* - Allows to set a higher initial burst limit by setting `maxTokens` higher
* than `refillRate`
*
* Adapted from the Krakend tokenBucket algorithm to include a variable cost:
* @see https://github.com/krakend/krakend-ratelimit/blob/369f0be9b51a4fb8ab7d43e4833d076b461a4374/rate.go#L85
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This one shouldn't have been put here. It's moved to the describe block for the custom algorithm.

Base automatically changed from nv-3061-rate-limiting-nestjs-guard to next December 5, 2023 07:16
@rifont rifont merged commit e51b43e into next Dec 6, 2023
@rifont rifont deleted the nv-3060-variable-cost-limiter branch December 6, 2023 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants