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 +1 -0
- data/LICENSE.txt +2 -2
- data/README.md +70 -16
- data/lib/pause/action.rb +3 -0
- data/lib/pause/redis/adapter.rb +2 -0
- data/lib/pause/version.rb +1 -1
- data/pause.gemspec +4 -4
- data/spec/pause/action_spec.rb +9 -0
- metadata +7 -4
data/.gitignore
CHANGED
data/LICENSE.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2012
|
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.
|
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
|
-
|
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
|
-
|
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 =
|
128
|
+
action = NotifyViaEmailAction.new("thing")
|
113
129
|
|
114
130
|
while true
|
115
131
|
action.increment!
|
116
132
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
puts
|
121
|
-
puts
|
122
|
-
puts
|
123
|
-
|
124
|
-
|
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
|
+
|
data/lib/pause/action.rb
CHANGED
data/lib/pause/redis/adapter.rb
CHANGED
data/lib/pause/version.rb
CHANGED
data/pause.gemspec
CHANGED
@@ -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
|
12
|
-
gem.summary = %q(
|
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($/)
|
data/spec/pause/action_spec.rb
CHANGED
@@ -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
|
+
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-
|
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
|
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:
|
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
|