redis-throttler 0.1.4 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c2efd874b28958cb9d0e7131dc9131162b51a6b8
4
- data.tar.gz: 37a41540b2955373ab62e7acda33bfa404e6f931
3
+ metadata.gz: c3648bd744161a9c30693fd53fb7bfb490ab54f3
4
+ data.tar.gz: 1b7c3424b04912da66ed591db45e14a36e518e5d
5
5
  SHA512:
6
- metadata.gz: 4cdf4289b6e02e4b75a2f57dbd61b6de2f4b770b99dfea206d77a826099549647513022295dc5a4c272e5f713e07409cdf0bf61495db8536827da9a8aca7779d
7
- data.tar.gz: a4e7b42fbbdde3a74ada112876b67a26d65dc42dfecdb3c227d19e378e5c7299981aa7b07966c35d0002ba0ad1be7b542647333fe2f48c166708bca1ea7e9e63
6
+ metadata.gz: 7bff0a3c2299681c052da3d0333f6c7858ae5b689a2c434ead0e18de1bcfa83dbc94b9f241afb4509fe05c5eef3c2d5b9c5cd18cc0faf6b2aae0b20abd8f0bfa
7
+ data.tar.gz: bf0eac98c7f1cff826f20c79330f135b4252d58d8b47b77ce45000e20521d0df3dffebd52450efd4abb1d330e0217c2c289d15600d426a8f27610cf3af6ee1cd
@@ -0,0 +1,120 @@
1
+ module RedisThrottler
2
+ class Base
3
+ # Create a RedisThrottler object.
4
+ #
5
+ # @param [String] key A name to uniquely identify this rate limit. For example, 'emails'
6
+ # @param [Hash] options Options hash
7
+ # @option options [Integer] :bucket_span (600) Time span to track in seconds
8
+ # @option options [Integer] :bucket_interval (5) How many seconds each bucket represents
9
+ # @option options [Integer] :bucket_expiry (@bucket_span) How long we keep data in each bucket before it is auto expired. Cannot be larger than the bucket_span.
10
+ # @option options [Redis] :redis (nil) Redis client if you need to customize connection options
11
+ #
12
+ # @return [RedisThrottler] RedisThrottler instance
13
+ #
14
+ def initialize(key, options = {})
15
+ @key = key
16
+ @bucket_span = options[:bucket_span] || 600
17
+ @bucket_interval = options[:bucket_interval] || 5
18
+ @bucket_expiry = options[:bucket_expiry] || @bucket_span
19
+ if @bucket_expiry > @bucket_span
20
+ raise ArgumentError.new("Bucket expiry cannot be larger than the bucket span")
21
+ end
22
+ @bucket_count = (@bucket_span / @bucket_interval).round
23
+ if @bucket_count < 3
24
+ raise ArgumentError.new("Cannot have less than 3 buckets")
25
+ end
26
+ @redis = options[:redis]
27
+ end
28
+
29
+ # Increment counter for a given subject.
30
+ #
31
+ # @param [String] subject A unique key to identify the subject. For example, 'user@foo.com'
32
+ # @param [Integer] count The number by which to increment the counter
33
+ #
34
+ # @return [Integer] increments within interval
35
+ def add(subject, count = 1)
36
+ bucket = get_bucket
37
+ subject = "#{@key}:#{subject}"
38
+ redis.pipelined do
39
+ redis.hincrby(subject, bucket, count)
40
+ redis.hdel(subject, (bucket + 1) % @bucket_count)
41
+ redis.hdel(subject, (bucket + 2) % @bucket_count)
42
+ redis.expire(subject, @bucket_expiry)
43
+ end.first
44
+ end
45
+
46
+ # Returns the count for a given subject and interval
47
+ #
48
+ # @param [String] subject Subject for the count
49
+ # @param [Integer] interval How far back (in seconds) to retrieve activity.
50
+ #
51
+ # @return [Integer] current count for subject
52
+ def count(subject, interval)
53
+ bucket = get_bucket
54
+ interval = [interval, @bucket_interval].max
55
+ count = (interval / @bucket_interval).floor
56
+ subject = "#{@key}:#{subject}"
57
+
58
+ keys = (0..count - 1).map do |i|
59
+ (bucket - i) % @bucket_count
60
+ end
61
+ redis.hmget(subject, *keys).inject(0) {|a, i| a + i.to_i}
62
+ end
63
+
64
+ # Check if the rate limit has been exceeded.
65
+ #
66
+ # @param [String] subject Subject to check
67
+ # @param [Hash] options Options hash
68
+ # @option options [Integer] :interval How far back to retrieve activity.
69
+ # @option options [Integer] :threshold Maximum number of actions
70
+ #
71
+ # @return [Boolean] true if exceeded
72
+ def exceeded?(subject, options = {})
73
+ count(subject, options[:interval]) >= options[:threshold]
74
+ end
75
+
76
+ # Check if the rate limit is within bounds
77
+ #
78
+ # @param [String] subject Subject to check
79
+ # @param [Hash] options Options hash
80
+ # @option options [Integer] :interval How far back to retrieve activity.
81
+ # @option options [Integer] :threshold Maximum number of actions
82
+ #
83
+ # @return [Integer] true if within bounds
84
+ def within_bounds?(subject, options = {})
85
+ !exceeded?(subject, options)
86
+ end
87
+
88
+ # Execute a block once the rate limit is within bounds
89
+ # *WARNING* This will block the current thread until the rate limit is within bounds.
90
+ #
91
+ # @param [String] subject Subject for this rate limit
92
+ # @param [Hash] options Options hash
93
+ # @option options [Integer] :interval How far back to retrieve activity.
94
+ # @option options [Integer] :threshold Maximum number of actions
95
+ # @yield The block to be run
96
+ #
97
+ # @example Send an email as long as we haven't send 5 in the last 10 minutes
98
+ # RedisThrottler.exec_with_threshold(email, [:threshold => 5, :interval => 600]) do
99
+ # send_another_email
100
+ # end
101
+ def exec_within_threshold(subject, options = {}, &block)
102
+ options[:threshold] ||= 30
103
+ options[:interval] ||= 30
104
+ while exceeded?(subject, options)
105
+ sleep @bucket_interval
106
+ end
107
+ yield(self)
108
+ end
109
+
110
+ private
111
+
112
+ def get_bucket(time = Time.now.to_i)
113
+ ((time % @bucket_span) / @bucket_interval).floor
114
+ end
115
+
116
+ def redis
117
+ @redis ||= Redis.new(host: '192.168.99.100', port: 32771)
118
+ end
119
+ end
120
+ end
@@ -1,6 +1,4 @@
1
- require 'redis-throttler'
2
-
3
- class RedisThrottler
1
+ module RedisThrottler
4
2
  module Model
5
3
 
6
4
  def self.included(base)
@@ -20,7 +18,7 @@ class RedisThrottler
20
18
  threshold = opts[:for] || 900
21
19
  interval = opts[:interval] || 5
22
20
 
23
- limiter = RedisThrottler.new("#{klass}:#{key}", bucket_interval: interval, bucket_span: threshold)
21
+ limiter = RedisThrottler::Base.new("#{klass}:#{key}", bucket_interval: interval, bucket_span: threshold)
24
22
  @limits[key] = "#{subject.to_s} limit #{limit} per #{threshold} sec"
25
23
 
26
24
  # includes('?') will return true
@@ -1,3 +1,3 @@
1
- class RedisThrottler
2
- VERSION = '0.1.4'
1
+ module RedisThrottler
2
+ VERSION = '0.1.5'
3
3
  end
@@ -1,120 +1,9 @@
1
1
  require 'redis'
2
+ require 'redis-throttler/model'
3
+ require 'redis-throttler/base'
2
4
 
3
- class RedisThrottler
4
- # Create a RedisThrottler object.
5
- #
6
- # @param [String] key A name to uniquely identify this rate limit. For example, 'emails'
7
- # @param [Hash] options Options hash
8
- # @option options [Integer] :bucket_span (600) Time span to track in seconds
9
- # @option options [Integer] :bucket_interval (5) How many seconds each bucket represents
10
- # @option options [Integer] :bucket_expiry (@bucket_span) How long we keep data in each bucket before it is auto expired. Cannot be larger than the bucket_span.
11
- # @option options [Redis] :redis (nil) Redis client if you need to customize connection options
12
- #
13
- # @return [RedisThrottler] RedisThrottler instance
14
- #
15
- def initialize(key, options = {})
16
- @key = key
17
- @bucket_span = options[:bucket_span] || 600
18
- @bucket_interval = options[:bucket_interval] || 5
19
- @bucket_expiry = options[:bucket_expiry] || @bucket_span
20
- if @bucket_expiry > @bucket_span
21
- raise ArgumentError.new("Bucket expiry cannot be larger than the bucket span")
22
- end
23
- @bucket_count = (@bucket_span / @bucket_interval).round
24
- if @bucket_count < 3
25
- raise ArgumentError.new("Cannot have less than 3 buckets")
26
- end
27
- @redis = options[:redis]
28
- end
29
-
30
- # Increment counter for a given subject.
31
- #
32
- # @param [String] subject A unique key to identify the subject. For example, 'user@foo.com'
33
- # @param [Integer] count The number by which to increment the counter
34
- #
35
- # @return [Integer] increments within interval
36
- def add(subject, count = 1)
37
- bucket = get_bucket
38
- subject = "#{@key}:#{subject}"
39
- redis.pipelined do
40
- redis.hincrby(subject, bucket, count)
41
- redis.hdel(subject, (bucket + 1) % @bucket_count)
42
- redis.hdel(subject, (bucket + 2) % @bucket_count)
43
- redis.expire(subject, @bucket_expiry)
44
- end.first
45
- end
46
-
47
- # Returns the count for a given subject and interval
48
- #
49
- # @param [String] subject Subject for the count
50
- # @param [Integer] interval How far back (in seconds) to retrieve activity.
51
- #
52
- # @return [Integer] current count for subject
53
- def count(subject, interval)
54
- bucket = get_bucket
55
- interval = [interval, @bucket_interval].max
56
- count = (interval / @bucket_interval).floor
57
- subject = "#{@key}:#{subject}"
58
-
59
- keys = (0..count - 1).map do |i|
60
- (bucket - i) % @bucket_count
61
- end
62
- redis.hmget(subject, *keys).inject(0) {|a, i| a + i.to_i}
63
- end
64
-
65
- # Check if the rate limit has been exceeded.
66
- #
67
- # @param [String] subject Subject to check
68
- # @param [Hash] options Options hash
69
- # @option options [Integer] :interval How far back to retrieve activity.
70
- # @option options [Integer] :threshold Maximum number of actions
71
- #
72
- # @return [Boolean] true if exceeded
73
- def exceeded?(subject, options = {})
74
- count(subject, options[:interval]) >= options[:threshold]
75
- end
76
-
77
- # Check if the rate limit is within bounds
78
- #
79
- # @param [String] subject Subject to check
80
- # @param [Hash] options Options hash
81
- # @option options [Integer] :interval How far back to retrieve activity.
82
- # @option options [Integer] :threshold Maximum number of actions
83
- #
84
- # @return [Integer] true if within bounds
85
- def within_bounds?(subject, options = {})
86
- !exceeded?(subject, options)
87
- end
88
-
89
- # Execute a block once the rate limit is within bounds
90
- # *WARNING* This will block the current thread until the rate limit is within bounds.
91
- #
92
- # @param [String] subject Subject for this rate limit
93
- # @param [Hash] options Options hash
94
- # @option options [Integer] :interval How far back to retrieve activity.
95
- # @option options [Integer] :threshold Maximum number of actions
96
- # @yield The block to be run
97
- #
98
- # @example Send an email as long as we haven't send 5 in the last 10 minutes
99
- # RedisThrottler.exec_with_threshold(email, [:threshold => 5, :interval => 600]) do
100
- # send_another_email
101
- # end
102
- def exec_within_threshold(subject, options = {}, &block)
103
- options[:threshold] ||= 30
104
- options[:interval] ||= 30
105
- while exceeded?(subject, options)
106
- sleep @bucket_interval
107
- end
108
- yield(self)
109
- end
110
-
111
- private
112
-
113
- def get_bucket(time = Time.now.to_i)
114
- ((time % @bucket_span) / @bucket_interval).floor
115
- end
116
-
117
- def redis
118
- @redis ||= Redis.new(host: '192.168.99.100', port: 32771)
5
+ module RedisThrottler
6
+ def self.included(base)
7
+ base.include(RedisThrottler::Model)
119
8
  end
120
9
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require 'rspec'
2
2
  require 'redis-throttler'
3
+ require 'redis-throttler/base'
3
4
 
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe RedisThrottler do
4
4
 
5
5
  before do
6
- @rl = RedisThrottler.new('test')
6
+ @rl = RedisThrottler::Base.new('test')
7
7
  @rl.send(:redis).flushdb
8
8
  end
9
9
 
@@ -13,13 +13,13 @@ describe RedisThrottler do
13
13
 
14
14
  it 'should not allow bucket count less than 3' do
15
15
  expect do
16
- RedisThrottler.new('test', {:bucket_span => 1, :bucket_interval => 1})
16
+ RedisThrottler::Base.new('test', {:bucket_span => 1, :bucket_interval => 1})
17
17
  end.to raise_error(ArgumentError)
18
18
  end
19
19
 
20
20
  it 'should not allow bucket expiry to be larger than the bucket span' do
21
21
  expect do
22
- RedisThrottler.new("key", {:bucket_expiry => 1200})
22
+ RedisThrottler::Base.new("key", {:bucket_expiry => 1200})
23
23
  end.to raise_error(ArgumentError)
24
24
  end
25
25
 
@@ -94,7 +94,7 @@ describe RedisThrottler do
94
94
  # end
95
95
 
96
96
  it 'counts correclty if bucket_span equals count-interval ' do
97
- @rl = RedisThrottler.new('key', {:bucket_span => 10, bucket_interval: 1})
97
+ @rl = RedisThrottler::Base.new('key', {:bucket_span => 10, bucket_interval: 1})
98
98
  @rl.add('value1')
99
99
  expect(@rl.count('value1', 10)).to eql(1)
100
100
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-throttler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Surdam
@@ -114,6 +114,7 @@ files:
114
114
  - Rakefile
115
115
  - gemspec.yml
116
116
  - lib/redis-throttler.rb
117
+ - lib/redis-throttler/base.rb
117
118
  - lib/redis-throttler/model.rb
118
119
  - lib/redis-throttler/version.rb
119
120
  - redis-throttler.gemspec