-
Notifications
You must be signed in to change notification settings - Fork 2
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
Extract Adapters #18
Extract Adapters #18
Conversation
Move them into a module and a directory. This will allow us to expand the adapter corpus shortly.
so that the user is not always at the mercy of auto-detection
Rule of 3
99ab0f8
to
5a4bb2b
Compare
@julik would it be possible to break this up into a separate Pull Requests? |
It is possible, but how useful is it to do so? And where would the separation between the pull requests be, if there are dependencies between changed components? |
Coming to think of it - I can take out the Redis adapter into a separate PR, but not much else 🤔 will that work for you? |
@@ -90,12 +90,14 @@ def accepted? | |||
# the bucket contents will then be capped at this value. So with | |||
# bucket_capacity set to 12 and a `fillup(14)` the bucket will reach the level | |||
# of 12, and will then immediately start leaking again. | |||
def initialize(key:, capacity:, leak_rate: nil, over_time: nil) | |||
# @param adapter[Pecorino::Adapters::BaseAdapter] a compatible adapter | |||
def initialize(key:, capacity:, adapter: Pecorino.adapter, leak_rate: nil, over_time: nil) |
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.
question: a question not related to this PR, more of a general question for LeakyBucket. I think our previous implementation allowed LeakyBucket to overfill, but you've fixed it, and now it doesn't do that any more.
If value is higher than capacity, it will refuse to fill a bucket.
I can imagine situations, where it would be great if leaky bucket could overfill. For example, if we're implementing a leaky bucket for API rate limiting, you might allow a user to exceed their rate limit by a small margin and then only enforce the limit if they continue to exceed it. This gives a bit of leeway for users who occasionally go over their limits but still protects your system from being overwhelmed by too many requests.
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.
A bucket can never overfill because all fillups get clamped to the capacity value. We have 2 methods - one for conditional fillup (where if your fillup "would" make the bucket overflow it gets refused) and one for unconditional fillup - it will only tell you whether the bucket did spillover as a result of your call. Having these two allows us to implement what you are describing, no? Or I am missing something and "Exceeding by a small margin" needs more information.
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.
unconditional fillup - it will only tell you whether the bucket did spillover as a result of your call.
This is precisely what I was looking for.
sleep 0.65 | ||
|
||
# Both the leaky bucket and the block should have expired by now, and `prune` should not raise | ||
adapter.prune |
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.
nitpick: we don't assert anything in this test. Is this is intentional? What's the point then to have it?
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.
There is a whole discussion about assert_nothing_raised
here https://stackoverflow.com/questions/12499259/why-doesnt-minitestspec-have-a-wont-raise-assertion - basically, Ryan posits that if your call raises something, it means the test will fail anyway (and using "assert_nothing_raised" just inflates your green dot count and gives you fake satisfaction for no reason, which is not to be had on these premises!)
That's why I left out the assertion (I think it comes with the activesupport methods, which we do not import into the test suite just now). But I could add it if you think just "calling the thing" is not telling the reader what we are doing. Or we could stick in a comment?..
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.
How do you feel about assert adapter.prune
?
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.
Can do - we would need the prune
method to return a truthy value for this.
def set_block(key:, block_for:) | ||
end | ||
|
||
# Returns the time until which a block for a given key is in effect. If there is no block in |
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.
nitpick: it's cool that you're writting these comments, but would it be better to have yard definitions here instead?
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.
Why not both? 😆 good one
|
||
* A memory store (good enough if you have just 1 process) | ||
* A PostgreSQL or SQLite database (at the moment there is no MySQL support, we would be delighted if you could add it) | ||
* A Redis instance | ||
|
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.
nitpick: might be a good idea to explain how to redefine adapter - in case of redis, as example, adapter will not be inherited from ActiveRecord and requires a manual configuration.
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.
None of them inherit from ActiveRecord anymore, some just use the ActiveRecord classes as means of passing connection configuration (and to piggyback on escaping)
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.
None of them inherit from ActiveRecord anymore
Why are you saying that? there is a default_adapter_from_main_database
method still defined.
But it would be great to add a section about how to configure adapter for pecorino and which settings are being configured by default.
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.
There is that method indeed (to let users have their adapter picked automatically), but nothing inherits anything there?..
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.
Sorry for the confusion, indeed wrong use of the word "inherits".
@julik checking almost 1000 line of code was a bit overwhelming, but I did my best :) |
end | ||
|
||
def create_tables(active_record_schema) | ||
active_record_schema.create_table :pecorino_leaky_buckets, id: :uuid do |t| |
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.
suggestion: would it be possible not to force :uuid as a primary key on users? Make it configurable?
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.
Yeah let's switch to whatever ID structure that is the app defalut 👍
active_record_schema.add_index :pecorino_leaky_buckets, [:key], unique: true | ||
active_record_schema.add_index :pecorino_leaky_buckets, [:may_be_deleted_after] | ||
|
||
active_record_schema.create_table :pecorino_blocks, id: :uuid do |t| |
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.
suggestion: Same suggestion here -- would be great not to force :uuid as a primary key on users and make this configurable.
|
||
require_relative "base_adapter" | ||
require "digest" | ||
require "redis" |
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.
Would be great to surround this require with begin rescue. This would help us to throw a meaningful error, if redis gem is missing in gemfile.
begin
require "redis"
rescue LoadError
puts "Please include redis gem in Gemfile"
exit
end
|
||
* A memory store (good enough if you have just 1 process) | ||
* A PostgreSQL or SQLite database (at the moment there is no MySQL support, we would be delighted if you could add it) | ||
* A Redis instance | ||
|
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.
Sorry for the confusion, indeed wrong use of the word "inherits".
This also adds a Redis adapter and a Memory adapter. The Redis adapter is useful since it can be a continuation of Prorate in a way - but with conditional fillup added in.
The memory store is useful in a different way - it can be used as a reference implementation to test other adapters against.
Also make the adapter configurable on the
Pecorino
module.