-
Notifications
You must be signed in to change notification settings - Fork 153
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
feature: resty.limit.count counts made instead of remaining requests #34
base: master
Are you sure you want to change the base?
Conversation
|
||
if commit then | ||
remaining, err = dict:incr(key, -1, limit) | ||
if not remaining then | ||
count, err = dict:incr(key, 1, 0, window) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Referring to #21, the reason why this module used both incr
and expire
is that incr
didn't support init_ttl
yet, I think.
So if you use init_ttl
here, L53-L59 are not necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. I was using latest stable OpenResty and this got in 1.13.6.2 RC1. That explains why it was failing for me locally.
by keeping track of requests that were made instead of remaining ones it is possible to change the limit on the fly and share one counter between several count limiters with different limits ref: openresty#23 (comment)
lib/resty/limit/count.lua
Outdated
@@ -54,6 +54,9 @@ function _M.incoming(self, key, commit) | |||
end | |||
|
|||
if count > limit then | |||
if commit then | |||
self:uncommit(key) | |||
end | |||
return nil, "rejected" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if :uncommit
returns nil
and err
, this returns nil
and rejected
here. Is it ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. The request was rejected because it was over the limit and the worst thing that can happen is that the count is going to be more than expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should return err
here if :uncommit
returns err
.
The count in shdict is already incremented in L47, so it is important to know whether :uncommit
succeeded or not.
We can notice that the count equals the limit when this returns rejected
, and the count may be grater than the limit when this returns err
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the example code checks for "rejected" as the return value, so changing that would break existing code.
My point is that if the call is rejected once, all other requests to the same key will be rejected too. There is not much value in keeping that value exact anymore if the call was rejected. The key will get expired in some time and a new key created with a new bucket and 0 as a value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I simply think this should be like the following.
Do we have the same understanding?
if count > limit then
if commit then
ok, err = self:uncommit(key)
if not ok then
return nil, err
end
end
return nil, "rejected"
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't. This method is expected to return nil, "rejected"
when the request is rejected.
If something breaks during the uncommit and returns something else like return nil, "error"
the code checking the return code as in the examples:
local delay, err = lim:incoming(key, true)
if err == "rejected" then
return ngx.exit(503)
end
Will fail to act on it. It will have to handle it as a generic error - which is not true because the request was indeed rejected. So I believe the returned error should be "rejected"
even if it failed to uncommit the limit.
And why it does not matter that much?
If the key is incremented to value 5
and the limit is 5
, then the request comes through.
If the next request is incremented to 6
then it is rejected. If it fails to uncommit, the value stays 6
. It has no way, to get back under 5
so it would be allowed again. The value will be > 5
until the key expires and gets deleted, then another request will set it to 1
.
I can see this being an issue only in some credit based system, where you are actually decrementing the credits and every one counts. But then you need a more reliable system like a database with transactions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And why it does not matter that much?
I think one of the important original purpose of this PR is to offer the flexibility of using different limits on the same key. ref: #23 (comment)
In this case, it does matter much.
However, I understand that it shouldn't be changed in order to follow the examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@y-tabata so what would happen if it fails to uncommit? It would still be incremented already.
I understand that it is important to try to keep the real value in some use cases. That's why I'm trying to fix it in this pull request.
Even our code is just checking the "rejected" or returning invalid configuration: https://github.com/3scale/apicast/blob/c4231f040b2ae37bc865ee64f01797b7c69afd98/gateway/src/apicast/policy/rate_limit/rate_limit.lua#L189-L197
Our disagreement is only about the return value. If it failed to uncommit, then there is not much that we can do. The request will be correctly rejected, but the value will be +1 for the future.
The same happens if we would return some different error message. I wonder how you'd want to handle that case to make the situation better somehow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is important for a user to notice there is some configuration issue.
It may be difficult to decrement the count automatically. However if the user notices that, the user can check the configuration or decrement the count manually.
By the way, the conn.lua
returns err
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user also loses the information if the request was rejected or not.
But the argument of conn.lua
returning err
is a good one. Will update the PR.
600d50c
to
ed307cc
Compare
Hi @mikz I think this PR is good for us to share a counter for difference rate limit policy, just like limit:conn. And could you split the |
|
||
if commit then | ||
remaining, err = dict:incr(key, -1, limit) | ||
if not remaining then | ||
count, err = dict:incr(key, 1, 0, window) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should support the old version like core lib, maybe we can have two difference incoming
for difference version of Openersty
@rainingmaster thanks for the feedback. I no longer work on this, but someone from https://github.com/3scale/APIcast could take over. /cc @eloycoto |
And allow the script to use current directory's code instead of download it
by keeping track of requests that were made instead of remaining ones
it is possible to change the limit on the fly and share one counter
between several count limiters with different limits
It is possible all the details are not right, I didn't really understand the part of calling
DICT:expire
.But I'd at least like to verify the approach and open a discussion.
ref: #23 (comment)