pause 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  .idea
19
+ .DS_Store
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 TODO: Write your name
1
+ Copyright (c) 2012 Wanelo, Inc
2
2
 
3
3
  MIT License
4
4
 
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -3,9 +3,15 @@ Pause
3
3
 
4
4
  [![Build status](https://secure.travis-ci.org/wanelo/pause.png)](http://travis-ci.org/wanelo/pause)
5
5
 
6
- Pause is a Redis-backed rate-limiting client. Use it to track events, with
6
+ Pause is a flexible Redis-backed rate-limiting client. Use it to track events, with
7
7
  rules around how often they are allowed to occur within configured time checks.
8
8
 
9
+ Because Pause is Redis-based, multiple ruby processes (even distributed across multiple servers) can track and report
10
+ events together, and then query whether a particular identifier should be rate limited or not.
11
+
12
+ Sample applications include IP-based blocking based on HTTP request volume (see related gem "spanx"),
13
+ throttling push notifications as to not overwhelm the user with too much frequency, etc.
14
+
9
15
  ## Installation
10
16
 
11
17
  Add this line to your application's Gemfile:
@@ -36,16 +42,15 @@ Pause.configure do |config|
36
42
  config.redis_host = "127.0.0.1"
37
43
  config.redis_port = 6379
38
44
  config.redis_db = 1
39
-
40
- config.resolution = 600
41
- config.history = 86400
45
+ config.resolution = 600 # aggregate all events into 10 minute blocks
46
+ config.history = 86400 # discard all events older than 1 day
42
47
  end
43
48
  ```
44
49
 
45
50
  ### Actions
46
51
 
47
52
  Define local actions for your application. Actions define a scope by
48
- which they are identified in the persistent store, and a set of checks. Checks define various
53
+ which they are identified in the persistent store (aka "namespace"), and a set of checks. Checks define various
49
54
  thresholds (`max_allowed`) against periods of time (`period_seconds`). When a threshold it triggered,
50
55
  the action is rate limited, and stays rate limited for the duration of `block_ttl` seconds.
51
56
 
@@ -62,9 +67,16 @@ Checks are configured with the following arguments (which can be passed as an ar
62
67
  Scope is simple string used to identify this action in the Redis store, and is appended to all keys.
63
68
  Therefore it is advised to keep scope as short as possible to reduce memory requirements of the store.
64
69
 
70
+ If you are using the same Redis store to rate limit multiple actions, you must ensure that each action
71
+ has a unique scope.
72
+
65
73
  #### Resolution
66
74
 
67
- Note that your resolution must be less than or equal to the smallest `period_seconds` value in your checks.
75
+ Resolution is the period of aggregation. As events come in, Pause aggregates them in time blocks
76
+ of this length. If you set resolution to 10 minutes, all events arriving within a 10 minute block
77
+ are aggregated.
78
+
79
+ Resolution must be less than or equal to the smallest `period_seconds` value in your checks.
68
80
  In other words, if your shortest check is 1 minute, you could set resolution to 1 minute or smaller.
69
81
 
70
82
  #### Example
@@ -88,6 +100,7 @@ class FollowsController < ApplicationController
88
100
  action = FollowAction.new(user.id)
89
101
  if action.ok?
90
102
  # do stuff!
103
+ # and track it...
91
104
  action.increment!
92
105
  else
93
106
  # action is rate limited, either skip
@@ -99,7 +112,10 @@ end
99
112
  class OtherController < ApplicationController
100
113
  def index
101
114
  action = OtherAction.new(params[:thing])
102
- if action.ok?
115
+ unless action.rate_limited?
116
+ # perform business logic
117
+ ....
118
+ # track it
103
119
  action.increment!(params[:count].to_i, Time.now.to_i)
104
120
  end
105
121
  end
@@ -109,25 +125,55 @@ end
109
125
  If more data is needed about why the action is blocked, the `analyze` can be called
110
126
 
111
127
  ```ruby
112
- action = MyAction.new("thing")
128
+ action = NotifyViaEmailAction.new("thing")
113
129
 
114
130
  while true
115
131
  action.increment!
116
132
 
117
- blocked_action = action.analyze
118
-
119
- if blocked_action
120
- puts blocked_action.identifier
121
- puts blocked_action.sum
122
- puts blocked_action.timestamp
123
-
124
- puts blocked_aciton.period_check.inspect
133
+ rate_limit_event = action.analyze
134
+ if rate_limit_event
135
+ puts rate_limit_event.identifier # which key got rate limited ("thing")
136
+ puts rate_limit_event.sum # total count that triggered a rate limit
137
+ puts rate_limit_event.timestamp # timestamp when rate limiting occurred
138
+ puts rate_limit_event.period_check # period check object, that triggered this rate limiting event
139
+ else
140
+ # not rate-limited, same as action.ok?
125
141
  end
126
142
 
127
143
  sleep 1
128
144
  end
129
145
  ```
130
146
 
147
+ ## Enabling/Disabling Actions
148
+
149
+ Actions have a built-in way by which they can be disabled or enabled.
150
+
151
+ ```ruby
152
+ MyAction.disable
153
+ MyAction.enable
154
+ ```
155
+
156
+ This is persisted to Redis, so state is not process-bound, but shared across all ruby run-times using this
157
+ action (assuming Redis store configuration is the same).
158
+
159
+ When disabled, Pause does *not* check state in any of its methods, so calls to increment! or ok? still work
160
+ exactly as before. This is because adding extra Redis calls can be expensive in loops. You should check
161
+ whether your action is enabled or disabled if it important to support enabling and disabling of rate limiting in
162
+ your context.
163
+
164
+ ```ruby
165
+ while true
166
+ if MyAction.enabled?
167
+ Thing.all.each do |thing|
168
+ action = MyAction.new(thing.name)
169
+ action.increment! unless action.rate_limited?
170
+ end
171
+ end
172
+ sleep 10
173
+ end
174
+ ```
175
+
176
+
131
177
  ## Contributing
132
178
 
133
179
  Want to make it better? Cool. Here's how:
@@ -137,3 +183,11 @@ Want to make it better? Cool. Here's how:
137
183
  3. Commit your changes (`git commit -am 'Add some feature'`)
138
184
  4. Push to the branch (`git push origin my-new-feature`)
139
185
  5. Create a new pull request
186
+
187
+ ## Authors
188
+
189
+ This gem was written by Eric Saxby, Atasay Gokkaya and Konstantin Gredeskoul at Wanelo, Inc.
190
+
191
+ Please see the LICENSE.txt file for further details.
192
+
193
+
@@ -67,6 +67,9 @@ module Pause
67
67
 
68
68
  def ok?
69
69
  Pause.analyzer.check(self).nil?
70
+ rescue ::Redis::CannotConnectError => e
71
+ $stderr.puts "Error connecting to redis: #{e.inspect}"
72
+ false
70
73
  end
71
74
 
72
75
  def analyze
@@ -2,6 +2,8 @@ require 'pause/helper/timing'
2
2
 
3
3
  module Pause
4
4
  module Redis
5
+
6
+ # This class encapsulates Redis operations used by Pause
5
7
  class Adapter
6
8
 
7
9
  include Pause::Helper::Timing
@@ -1,3 +1,3 @@
1
1
  module Pause
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -6,10 +6,10 @@ require 'pause/version'
6
6
  Gem::Specification.new do |gem|
7
7
  gem.name = "pause"
8
8
  gem.version = Pause::VERSION
9
- gem.authors = ["Atasay Gokkaya", "Paul Henry", "Eric Saxby"]
10
- gem.email = %w(atasay@wanelo.com paul@wanelo.com sax@wanelo.com)
11
- gem.description = %q(Real time redis rate limiting)
12
- gem.summary = %q(Real time redis rate limiting)
9
+ gem.authors = ["Atasay Gokkaya", "Paul Henry", "Eric Saxby", "Konstantin Gredeskoul"]
10
+ gem.email = %w(atasay@wanelo.com paul@wanelo.com sax@wanelo.com kig@wanelo.com)
11
+ gem.description = %q(Real time rate limiting for multi-process ruby environments based on Redis)
12
+ gem.summary = %q(RReal time rate limiting for multi-process ruby environments based on Redis)
13
13
  gem.homepage = "https://github.com/wanelo/pause"
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
@@ -60,6 +60,15 @@ describe Pause::Action do
60
60
  action.ok?.should be_false
61
61
 
62
62
  end
63
+
64
+ it "should return false and silently fail if redis is not available" do
65
+ Redis.any_instance.stub(:zrange) { raise Redis::CannotConnectError }
66
+ time = period_marker(resolution, Time.now.to_i)
67
+
68
+ action.increment! 4, time - 25
69
+
70
+ action.ok?.should be_false
71
+ end
63
72
  end
64
73
 
65
74
  describe "#analyze" do
metadata CHANGED
@@ -1,17 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pause
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Atasay Gokkaya
9
9
  - Paul Henry
10
10
  - Eric Saxby
11
+ - Konstantin Gredeskoul
11
12
  autorequire:
12
13
  bindir: bin
13
14
  cert_chain: []
14
- date: 2012-11-14 00:00:00.000000000 Z
15
+ date: 2012-11-26 00:00:00.000000000 Z
15
16
  dependencies:
16
17
  - !ruby/object:Gem::Dependency
17
18
  name: redis
@@ -109,11 +110,13 @@ dependencies:
109
110
  - - ! '>='
110
111
  - !ruby/object:Gem::Version
111
112
  version: '0'
112
- description: Real time redis rate limiting
113
+ description: Real time rate limiting for multi-process ruby environments based on
114
+ Redis
113
115
  email:
114
116
  - atasay@wanelo.com
115
117
  - paul@wanelo.com
116
118
  - sax@wanelo.com
119
+ - kig@wanelo.com
117
120
  executables: []
118
121
  extensions: []
119
122
  extra_rdoc_files: []
@@ -166,7 +169,7 @@ rubyforge_project:
166
169
  rubygems_version: 1.8.24
167
170
  signing_key:
168
171
  specification_version: 3
169
- summary: Real time redis rate limiting
172
+ summary: RReal time rate limiting for multi-process ruby environments based on Redis
170
173
  test_files:
171
174
  - spec/pause/action_spec.rb
172
175
  - spec/pause/analyzer_spec.rb