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

Allow customization of weight per request for rate limiting #505

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

jiang925
Copy link

@jiang925 jiang925 commented Nov 16, 2020

Use case:
We have an API to allow the client to fetch data by a list of ids. The API allows up to a few thousand ids. In order to better throttling experience, we want to throttle based on the number of ids being fetch. Add a weight option to specify the rate to deduct the limit.

Added tests and tests pass.

@grzuy
Copy link
Collaborator

grzuy commented Jun 15, 2021

Hi @jiang925 ,

Thank you very much for the contribution!

On the one hand this seems like an interesting feature, while on the other I have concerns about whether this may add unnecessary complexity to the library and the docs/API if this is not used a lot by users.

I want to give it some more thought though. 🤔

Thanks again!

Copy link

@jamiemccarthy jamiemccarthy left a comment

Choose a reason for hiding this comment

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

I would support this. It adds flexibility that would make Rack::Attack more useful for APIs in particular. I don't think it adds much complexity. Users who don't use it don't even need to know about it. And it would not change its performance at all, since increment already supports values other than 1. The only suggestion I have is to add one test.

(I use "we" and "our" here to mean people who care about Rack::Attack. :) )

If we're concerned about future compatibility and complexity, and think there might one day be additional parameters to the count method, we could define weight as an optional keyword argument (weight: 1) rather than an argument with a default (weight = 1). This would allow a 4th and 5th argument to be passed in without having to specify a nondefault weight. However this doesn't seem very likely. I think weight = 1 is fine.

A common user error might be to try to specify a noninteger weight (both memcache and redis support only integer values). We could .to_i the input before passing it to do_count, to try to minimize the impact of this mistake in a lightweight way.

And let's think about the edge case of weight 0. Because throttling depends on count returning the current value, invoking it with a weight of 0 can't short-circuit (unless we want to define that a weight=0 request never throttles, in which case we should short-circuit ourselves). I checked current versions of Rails' MemCacheStore and Dalli's Client, and good news, neither currently short-circuits. It might be worth adding a test to our suite checking for a 429. This would ensure that none of the configurations in Appraisal returns nil or 0 without hitting cache.

assert_equal 429, last_response.status
assert_nil last_response.headers["Retry-After"]
assert_equal "Retry later\n", last_response.body

Copy link

@jamiemccarthy jamiemccarthy Dec 12, 2021

Choose a reason for hiding this comment

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

Here's a good place to test that a weight of zero also returns a 429. (Add request.env["X-Lightweight"] to the lambda?)

Test suggestion: jamiemccarthy@6ec96eb

Copy link
Author

Choose a reason for hiding this comment

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

@jamiemccarthy Thank you for the suggestion and sorry for the delay! I missed all these notifications. I'll take a look soon and get back!

@ioquatix ioquatix deleted the branch rack:main February 1, 2022 20:05
@ioquatix ioquatix closed this Feb 1, 2022
@ioquatix ioquatix reopened this Feb 1, 2022
@ioquatix ioquatix changed the base branch from master to main February 1, 2022 20:11
@@ -244,7 +244,7 @@ Throttle state is stored in a [configurable cache](#cache-store-configuration) (

#### `throttle(name, options, &block)`

Name your custom throttle, provide `limit` and `period` as options, and make your ruby-block argument return the __discriminator__. This discriminator is how you tell rack-attack whether you're limiting per IP address, per user email or any other.
Name your custom throttle, provide `limit`, `period` and `weight` as options, and make your ruby-block argument return the __discriminator__. This discriminator is how you tell rack-attack whether you're limiting per IP address, per user email or any other.
Copy link

Choose a reason for hiding this comment

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

nit: may worth mention weight is an optional option

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.

5 participants