prorate 0.3.0 → 0.4.0
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/.rubocop.yml +2 -0
- data/.travis.yml +1 -2
- data/Rakefile +3 -2
- data/lib/prorate/null_logger.rb +5 -0
- data/lib/prorate/null_pool.rb +3 -1
- data/lib/prorate/throttle.rb +11 -20
- data/lib/prorate/throttled.rb +8 -0
- data/lib/prorate/version.rb +1 -1
- data/prorate.gemspec +4 -3
- data/scripts/bm.rb +1 -1
- data/scripts/bm_latency_lb_vs_mget.rb +5 -7
- data/scripts/reload_lua.rb +1 -1
- metadata +23 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 415042776ee7b18bf44e586fe148c822a44746bd
|
4
|
+
data.tar.gz: 7e249159cd2b18b699fc1f7b4fcb0f3923f2082d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f84dc40f3b0d789a2a8a3b0d9739b958958d895c2a43d2a60265a325465c46923ec8b5ce39fbf94d7942ce806315ffcb19bf39d4de9039853285aadfa8ecb933
|
7
|
+
data.tar.gz: 9335ecd1f3c25f082515dc5a7396aee2670748e6f287b481ef723699baaf355e5b67d0e23123721aefca894f0a2dd2e03065700ef581cad1b6bf9a64f963a1f9
|
data/.rubocop.yml
ADDED
data/.travis.yml
CHANGED
@@ -10,6 +10,5 @@ dist: trusty # https://docs.travis-ci.com/user/trusty-ci-environment/
|
|
10
10
|
sudo: false
|
11
11
|
cache: bundler
|
12
12
|
|
13
|
-
# Travis permits the following phases: before_install, install, after_install, before_script, script, after_script
|
14
13
|
script:
|
15
|
-
- bundle exec
|
14
|
+
- bundle exec rake
|
data/Rakefile
CHANGED
data/lib/prorate/null_logger.rb
CHANGED
data/lib/prorate/null_pool.rb
CHANGED
data/lib/prorate/throttle.rb
CHANGED
@@ -1,14 +1,6 @@
|
|
1
1
|
require 'digest'
|
2
2
|
|
3
3
|
module Prorate
|
4
|
-
class Throttled < StandardError
|
5
|
-
attr_reader :retry_in_seconds
|
6
|
-
def initialize(try_again_in)
|
7
|
-
@retry_in_seconds = try_again_in
|
8
|
-
super("Throttled, please lower your temper and try again in #{retry_in_seconds} seconds")
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
4
|
class ScriptHashMismatch < StandardError
|
13
5
|
end
|
14
6
|
|
@@ -16,49 +8,48 @@ module Prorate
|
|
16
8
|
end
|
17
9
|
|
18
10
|
class Throttle < Ks.strict(:name, :limit, :period, :block_for, :redis, :logger)
|
19
|
-
|
20
|
-
|
21
|
-
script_filepath = File.join(__dir__,"rate_limit.lua")
|
11
|
+
def self.lua_script_hash
|
12
|
+
script_filepath = File.join(__dir__, "rate_limit.lua")
|
22
13
|
script = File.read(script_filepath)
|
23
14
|
Digest::SHA1.hexdigest(script)
|
24
15
|
end
|
25
16
|
|
26
|
-
CURRENT_SCRIPT_HASH =
|
17
|
+
CURRENT_SCRIPT_HASH = lua_script_hash
|
27
18
|
|
28
19
|
def initialize(*)
|
29
20
|
super
|
30
21
|
@discriminators = [name.to_s]
|
31
22
|
self.redis = NullPool.new(redis) unless redis.respond_to?(:with)
|
32
|
-
raise MisconfiguredThrottle if (
|
23
|
+
raise MisconfiguredThrottle if (period <= 0) || (limit <= 0)
|
33
24
|
@leak_rate = limit.to_f / period # tokens per second;
|
34
25
|
end
|
35
|
-
|
26
|
+
|
36
27
|
def <<(discriminator)
|
37
28
|
@discriminators << discriminator
|
38
29
|
end
|
39
|
-
|
30
|
+
|
40
31
|
def throttle!
|
41
32
|
discriminator = Digest::SHA1.hexdigest(Marshal.dump(@discriminators))
|
42
33
|
identifier = [name, discriminator].join(':')
|
43
|
-
|
34
|
+
|
44
35
|
redis.with do |r|
|
45
36
|
logger.info { "Applying throttle counter %s" % name }
|
46
37
|
remaining_block_time, bucket_level = run_lua_throttler(redis: r, identifier: identifier, bucket_capacity: limit, leak_rate: @leak_rate, block_for: block_for)
|
47
38
|
|
48
39
|
if remaining_block_time > 0
|
49
40
|
logger.warn { "Throttle %s exceeded limit of %d in %d seconds and is blocked for the next %d seconds" % [name, limit, period, remaining_block_time] }
|
50
|
-
raise Throttled.new(remaining_block_time)
|
41
|
+
raise ::Prorate::Throttled.new(name, remaining_block_time)
|
51
42
|
end
|
52
|
-
|
43
|
+
return limit - bucket_level # How many calls remain
|
53
44
|
end
|
54
45
|
end
|
55
46
|
|
56
|
-
def run_lua_throttler(redis
|
47
|
+
def run_lua_throttler(redis:, identifier:, bucket_capacity:, leak_rate:, block_for:)
|
57
48
|
redis.evalsha(CURRENT_SCRIPT_HASH, [], [identifier, bucket_capacity, leak_rate, block_for])
|
58
49
|
rescue Redis::CommandError => e
|
59
50
|
if e.message.include? "NOSCRIPT"
|
60
51
|
# The Redis server has never seen this script before. Needs to run only once in the entire lifetime of the Redis server (unless the script changes)
|
61
|
-
script_filepath = File.join(__dir__,"rate_limit.lua")
|
52
|
+
script_filepath = File.join(__dir__, "rate_limit.lua")
|
62
53
|
script = File.read(script_filepath)
|
63
54
|
raise ScriptHashMismatch if Digest::SHA1.hexdigest(script) != CURRENT_SCRIPT_HASH
|
64
55
|
redis.script(:load, script)
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Prorate::Throttled < StandardError
|
2
|
+
attr_reader :throttle_name, :retry_in_seconds
|
3
|
+
def initialize(throttle_name, try_again_in)
|
4
|
+
@throttle_name = throttle_name
|
5
|
+
@retry_in_seconds = try_again_in
|
6
|
+
super("Throttled, please lower your temper and try again in #{retry_in_seconds} seconds")
|
7
|
+
end
|
8
|
+
end
|
data/lib/prorate/version.rb
CHANGED
data/prorate.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
require 'prorate/version'
|
@@ -29,8 +29,9 @@ Gem::Specification.new do |spec|
|
|
29
29
|
|
30
30
|
spec.add_dependency "ks"
|
31
31
|
spec.add_dependency "redis", ">= 2"
|
32
|
-
spec.add_development_dependency "connection_pool", "~>
|
32
|
+
spec.add_development_dependency "connection_pool", "~> 2"
|
33
33
|
spec.add_development_dependency "bundler", "~> 1.12"
|
34
|
-
spec.add_development_dependency "rake", "~>
|
34
|
+
spec.add_development_dependency "rake", "~> 12.3"
|
35
35
|
spec.add_development_dependency "rspec", "~> 3.0"
|
36
|
+
spec.add_development_dependency 'wetransfer_style', '0.6.0'
|
36
37
|
end
|
data/scripts/bm.rb
CHANGED
@@ -6,7 +6,7 @@ require 'redis'
|
|
6
6
|
require 'securerandom'
|
7
7
|
|
8
8
|
def average_ms(ary)
|
9
|
-
ary.map{|x| x*1000}.inject(0
|
9
|
+
ary.map { |x| x * 1000 }.inject(0, &:+) / ary.length
|
10
10
|
end
|
11
11
|
|
12
12
|
r = Redis.new
|
@@ -31,24 +31,23 @@ end
|
|
31
31
|
puts average_ms times
|
32
32
|
def key_for_ts(ts)
|
33
33
|
"th:%s:%d" % [@id, ts]
|
34
|
-
end
|
34
|
+
end
|
35
35
|
|
36
36
|
times = []
|
37
37
|
15.times do
|
38
|
-
id = SecureRandom.hex(10)
|
39
38
|
sec, _ = r.time # Use Redis time instead of the system timestamp, so that all the nodes are consistent
|
40
39
|
ts = sec.to_i # All Redis results are strings
|
41
40
|
k = key_for_ts(ts)
|
42
41
|
times << Benchmark.realtime {
|
43
42
|
r.multi do |txn|
|
44
|
-
# Increment the counter
|
43
|
+
# Increment the counter
|
45
44
|
txn.incr(k)
|
46
45
|
txn.expire(k, 120)
|
47
46
|
|
48
47
|
span_start = ts - 120
|
49
48
|
span_end = ts + 1
|
50
|
-
possible_keys = (span_start..span_end).map{|prev_time| key_for_ts(prev_time) }
|
51
|
-
|
49
|
+
possible_keys = (span_start..span_end).map { |prev_time| key_for_ts(prev_time) }
|
50
|
+
|
52
51
|
# Fetch all the counter values within the time window. Despite the fact that this
|
53
52
|
# will return thousands of elements for large sliding window sizes, the values are
|
54
53
|
# small and an MGET in Redis is pretty cheap, so perf should stay well within limits.
|
@@ -58,4 +57,3 @@ times = []
|
|
58
57
|
end
|
59
58
|
|
60
59
|
puts average_ms times
|
61
|
-
|
data/scripts/reload_lua.rb
CHANGED
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.
|
4
|
+
version: 0.4.0
|
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: 2019-08-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ks
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '2'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '2'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: bundler
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '12.3'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '12.3'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rspec
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: wetransfer_style
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.6.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.6.0
|
97
111
|
description: Can be used to implement all kinds of throttles
|
98
112
|
email:
|
99
113
|
- me@julik.nl
|
@@ -103,6 +117,7 @@ extra_rdoc_files: []
|
|
103
117
|
files:
|
104
118
|
- ".gitignore"
|
105
119
|
- ".rspec"
|
120
|
+
- ".rubocop.yml"
|
106
121
|
- ".travis.yml"
|
107
122
|
- Gemfile
|
108
123
|
- LICENSE.txt
|
@@ -115,6 +130,7 @@ files:
|
|
115
130
|
- lib/prorate/null_pool.rb
|
116
131
|
- lib/prorate/rate_limit.lua
|
117
132
|
- lib/prorate/throttle.rb
|
133
|
+
- lib/prorate/throttled.rb
|
118
134
|
- lib/prorate/version.rb
|
119
135
|
- prorate.gemspec
|
120
136
|
- scripts/bm.rb
|
@@ -141,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
157
|
version: '0'
|
142
158
|
requirements: []
|
143
159
|
rubyforge_project:
|
144
|
-
rubygems_version: 2.
|
160
|
+
rubygems_version: 2.6.11
|
145
161
|
signing_key:
|
146
162
|
specification_version: 4
|
147
163
|
summary: Time-restricted rate limiter using Redis
|