prorate 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Rakefile +5 -1
- data/lib/prorate/leaky_bucket.rb +2 -2
- data/lib/prorate/rate_limit.lua +18 -9
- data/lib/prorate/throttle.rb +9 -3
- data/lib/prorate/version.rb +1 -1
- data/prorate.gemspec +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 941127ba358ced7518e1c69224680aadb4beceac96127013f77f26dd8013619d
|
4
|
+
data.tar.gz: 9e517111477fc854e1404bedbd89126d0002f3d98386091180e757d9dcfdf992
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6478f0fb8c2df29254cae40e47ba5ffaa6bf09fce03543907edce681056e342eed6fdca57b4025449081c7c768f435ecb9e33cb2a76cc727500dd34b63c7b65c
|
7
|
+
data.tar.gz: 0e2a1376cd72013f9511b87a01143003c8ffe7a093004b9dbe13130008fef006e65452d1000e6998d997599f11120b883bca5f09eaeab5462b3eb6297f5e3cf7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 0.7.1
|
2
|
+
|
3
|
+
* Fix use of a ConnectionPool as `redis:` argument which was broken in 0.7.0
|
4
|
+
* Use the Lua KEYS argument in `rate_limit.lua` for future-proof clustering support
|
5
|
+
instead of computing the touched keys inside the Lua script.
|
6
|
+
|
1
7
|
# 0.7.0
|
2
8
|
|
3
9
|
* Add a naked `LeakyBucket` object which allows one to build sophisticated rate limiting relying
|
data/Rakefile
CHANGED
@@ -9,6 +9,10 @@ YARD::Rake::YardocTask.new(:doc) do |t|
|
|
9
9
|
t.files = ['lib/**/*.rb', '-', 'LICENSE.txt', 'CHANGELOG.md']
|
10
10
|
end
|
11
11
|
|
12
|
-
RSpec::Core::RakeTask.new(:spec)
|
12
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
13
|
+
spec.rspec_opts = ["-c", "--order=rand"]
|
14
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
15
|
+
end
|
16
|
+
|
13
17
|
RuboCop::RakeTask.new(:rubocop)
|
14
18
|
task default: [:spec, :rubocop]
|
data/lib/prorate/leaky_bucket.rb
CHANGED
@@ -60,7 +60,7 @@ module Prorate
|
|
60
60
|
# If your bucket is specific to a user, a browser or an IP address you need to mix in
|
61
61
|
# those values into the key prefix as appropriate.
|
62
62
|
# @param leak_rate[Float] the leak rate of the bucket, in tokens per second
|
63
|
-
# @param redis[Redis,#with] a Redis connection or a
|
63
|
+
# @param redis[Redis,#with] a Redis connection or a ConnectionPool instance
|
64
64
|
# if you are using the connection_pool gem. With a connection pool Prorate will
|
65
65
|
# checkout a connection using `#with` and check it in when it's done.
|
66
66
|
# @param bucket_capacity[Numeric] how many tokens is the bucket capped at.
|
@@ -70,7 +70,7 @@ module Prorate
|
|
70
70
|
# of 12, and will then immediately start leaking again.
|
71
71
|
def initialize(redis_key_prefix:, leak_rate:, redis:, bucket_capacity:)
|
72
72
|
@redis_key_prefix = redis_key_prefix
|
73
|
-
@redis =
|
73
|
+
@redis = redis.respond_to?(:with) ? redis : NullPool.new(redis)
|
74
74
|
@leak_rate = leak_rate.to_f
|
75
75
|
@capacity = bucket_capacity.to_f
|
76
76
|
end
|
data/lib/prorate/rate_limit.lua
CHANGED
@@ -8,13 +8,21 @@
|
|
8
8
|
redis.replicate_commands()
|
9
9
|
-- make some nicer looking variable names:
|
10
10
|
local retval = nil
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
local
|
11
|
+
|
12
|
+
-- Redis documentation recommends passing the keys separately so that Redis
|
13
|
+
-- can - in the future - verify that they live on the same shard of a cluster, and
|
14
|
+
-- raise an error if they are not. As far as can be understood this functionality is not
|
15
|
+
-- yet present, but if we can make a little effort to make ourselves more future proof
|
16
|
+
-- we should.
|
17
|
+
local bucket_level_key = KEYS[1]
|
18
|
+
local last_updated_key = KEYS[2]
|
19
|
+
local block_key = KEYS[3]
|
20
|
+
|
21
|
+
-- and the config variables
|
22
|
+
local max_bucket_capacity = tonumber(ARGV[1])
|
23
|
+
local leak_rate = tonumber(ARGV[2])
|
24
|
+
local block_duration = tonumber(ARGV[3])
|
25
|
+
local n_tokens = tonumber(ARGV[4]) -- How many tokens this call adds to the bucket. Defaults to 1
|
18
26
|
|
19
27
|
-- Take the Redis timestamp
|
20
28
|
local redis_time = redis.call("TIME") -- Array of [seconds, microseconds]
|
@@ -23,7 +31,8 @@ local key_lifetime = math.ceil(max_bucket_capacity / leak_rate)
|
|
23
31
|
|
24
32
|
local blocked_until = redis.call("GET", block_key)
|
25
33
|
if blocked_until then
|
26
|
-
|
34
|
+
-- see https://redis.io/docs/manual/programmability/lua-api/ : floats should be returned as strings or they will be cast into integers
|
35
|
+
return {(tostring(tonumber(blocked_until) - now)), -1}
|
27
36
|
end
|
28
37
|
|
29
38
|
-- get current bucket level. The throttle key might not exist yet in which
|
@@ -41,7 +50,7 @@ if (new_bucket_level + n_tokens) <= max_bucket_capacity then
|
|
41
50
|
retval = {0, math.ceil(new_bucket_level)}
|
42
51
|
else
|
43
52
|
redis.call("SETEX", block_key, block_duration, now + block_duration)
|
44
|
-
retval = {block_duration,
|
53
|
+
retval = {tostring(block_duration), -1}
|
45
54
|
end
|
46
55
|
|
47
56
|
-- Save the new bucket level
|
data/lib/prorate/throttle.rb
CHANGED
@@ -13,7 +13,7 @@ module Prorate
|
|
13
13
|
def initialize(name:, limit:, period:, block_for:, redis:, logger: Prorate::NullLogger)
|
14
14
|
@name = name.to_s
|
15
15
|
@discriminators = [name.to_s]
|
16
|
-
@redis =
|
16
|
+
@redis = redis.respond_to?(:with) ? redis : NullPool.new(redis)
|
17
17
|
@logger = logger
|
18
18
|
@block_for = block_for
|
19
19
|
|
@@ -95,7 +95,7 @@ module Prorate
|
|
95
95
|
block_for: @block_for,
|
96
96
|
n_tokens: n_tokens)
|
97
97
|
|
98
|
-
if
|
98
|
+
if bucket_level == -1
|
99
99
|
@logger.warn do
|
100
100
|
"Throttle %s exceeded limit of %d in %d seconds and is blocked for the next %d seconds" % [@name, @limit, @period, remaining_block_time]
|
101
101
|
end
|
@@ -135,9 +135,15 @@ module Prorate
|
|
135
135
|
end
|
136
136
|
|
137
137
|
def run_lua_throttler(identifier:, bucket_capacity:, leak_rate:, block_for:, n_tokens:)
|
138
|
+
# Computing the identifier is somewhat involved so we should avoid doing it too often
|
139
|
+
id = identifier
|
140
|
+
bucket_level_key = "#{id}.bucket_level"
|
141
|
+
last_updated_key = "#{id}.last_updated"
|
142
|
+
block_key = "#{id}.block"
|
143
|
+
|
138
144
|
@redis.with do |redis|
|
139
145
|
begin
|
140
|
-
redis.evalsha(LUA_SCRIPT_HASH, [], [
|
146
|
+
redis.evalsha(LUA_SCRIPT_HASH, keys: [bucket_level_key, last_updated_key, block_key], argv: [bucket_capacity, leak_rate, block_for, n_tokens])
|
141
147
|
rescue Redis::CommandError => e
|
142
148
|
if e.message.include? "NOSCRIPT"
|
143
149
|
# The Redis server has never seen this script before. Needs to run only once in the entire lifetime
|
data/lib/prorate/version.rb
CHANGED
data/prorate.gemspec
CHANGED
@@ -34,5 +34,5 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency "rspec", "~> 3.0"
|
35
35
|
spec.add_development_dependency 'wetransfer_style', '0.6.5'
|
36
36
|
spec.add_development_dependency 'yard', '~> 0.9'
|
37
|
-
spec.add_development_dependency 'pry', '~> 0.
|
37
|
+
spec.add_development_dependency 'pry', '~> 0.14.2'
|
38
38
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prorate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -114,14 +114,14 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.
|
117
|
+
version: 0.14.2
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.
|
124
|
+
version: 0.14.2
|
125
125
|
description: Can be used to implement all kinds of throttles
|
126
126
|
email:
|
127
127
|
- me@julik.nl
|
@@ -173,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
173
|
- !ruby/object:Gem::Version
|
174
174
|
version: '0'
|
175
175
|
requirements: []
|
176
|
-
rubygems_version: 3.0.3
|
176
|
+
rubygems_version: 3.0.3.1
|
177
177
|
signing_key:
|
178
178
|
specification_version: 4
|
179
179
|
summary: Time-restricted rate limiter using Redis
|