fresh_redis 0.0.6 → 0.0.7
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.
- data/Gemfile.lock +3 -3
- data/README.md +30 -8
- data/fresh_redis.gemspec +1 -1
- data/lib/fresh_redis/key.rb +0 -1
- data/lib/fresh_redis/string.rb +22 -2
- data/lib/fresh_redis/version.rb +1 -1
- data/spec/fresh_redis/string_spec.rb +70 -14
- metadata +6 -6
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fresh_redis (0.0.
|
4
|
+
fresh_redis (0.0.7)
|
5
5
|
redis
|
6
6
|
|
7
7
|
GEM
|
@@ -16,7 +16,7 @@ GEM
|
|
16
16
|
guard (>= 1.1)
|
17
17
|
rspec (~> 2.11)
|
18
18
|
listen (0.5.3)
|
19
|
-
mock_redis (0.5.
|
19
|
+
mock_redis (0.5.4)
|
20
20
|
rake (0.9.2.2)
|
21
21
|
rb-fsevent (0.9.2)
|
22
22
|
rb-inotify (0.8.8)
|
@@ -39,7 +39,7 @@ PLATFORMS
|
|
39
39
|
DEPENDENCIES
|
40
40
|
fresh_redis!
|
41
41
|
guard-rspec (= 2.1.0)
|
42
|
-
mock_redis (= 0.5.
|
42
|
+
mock_redis (= 0.5.4)
|
43
43
|
rake (= 0.9.2.2)
|
44
44
|
rb-fsevent
|
45
45
|
rb-inotify
|
data/README.md
CHANGED
@@ -26,6 +26,8 @@ Or install it yourself as:
|
|
26
26
|
|
27
27
|
### Simple counts
|
28
28
|
|
29
|
+
Increment a counter, but gradually expire portions of that counter. Useful for *"find count of X for the last Y minutes"* type queries, where some event occurs occasionally over a period of time.
|
30
|
+
|
29
31
|
```ruby
|
30
32
|
require "redis"
|
31
33
|
require "fresh_redis"
|
@@ -47,11 +49,32 @@ fresh.fsum "failed_login" # will return 2, cause the first incr has expired by n
|
|
47
49
|
|
48
50
|
### Hash operations
|
49
51
|
|
52
|
+
fresh_redis Hashes work similarly to existing Redis hashes, except that keys have individual expiry.
|
53
|
+
|
50
54
|
```ruby
|
51
|
-
|
52
|
-
|
55
|
+
require "redis"
|
56
|
+
require "fresh_redis"
|
57
|
+
fresh = FreshRedis.new(Redis.current)
|
53
58
|
|
54
|
-
|
59
|
+
fresh.fhset "temp_banned_users", "madlep", "too much cowbell"
|
60
|
+
# wait a bit
|
61
|
+
fresh.fhset "temp_banned_users", "normthegnome", "inappropriate office conduct"
|
62
|
+
fresh.fhset "temp_banned_users", "joeblogs", "suspected ninja"
|
63
|
+
|
64
|
+
# then straight away...
|
65
|
+
fresh.fhget "temp_banned_users", "madlep" # will return "threatening..."
|
66
|
+
fresh.fhget "temp_banned_users", "normthegnone" # will return "inappropriate..."
|
67
|
+
fresh.fhget "temp_banned_users", "joeblogs", # will return "suspected..."
|
68
|
+
fresh.fhgetall "temp_banned_users" # will return hash of {"madlep" => ..., "normthegnone" => ..., "joeblogs" => ...}
|
69
|
+
|
70
|
+
# wait for first fhset to expire
|
71
|
+
|
72
|
+
fresh.fhget "temp_banned_users", "madlep" # will return nil
|
73
|
+
fresh.fhget "temp_banned_users", "normthegnone" # will return "inappropriate..." - unchanged
|
74
|
+
fresh.fhget "temp_banned_users", "joeblogs", # will return "suspected..." - unchanged
|
75
|
+
fresh.fhgetall "temp_banned_users" # will return hash WITHOUT "madlep" as a key (just "normthegnone" and "joeblogs")
|
76
|
+
|
77
|
+
```
|
55
78
|
|
56
79
|
### Tweaking _"freshness"_ and _"granularity"_.
|
57
80
|
|
@@ -59,14 +82,13 @@ Think of it like stock rotation at your local supermarket. Freshness is how long
|
|
59
82
|
|
60
83
|
```ruby
|
61
84
|
# lets track douch users spamming the forum so we can do something about it...
|
85
|
+
fresh = FreshRedis.new(Redis.current, :freshness => 600, :granularity => 30)
|
62
86
|
|
63
87
|
# store post count for a user for 10 minutes (600 seconds), in buckets of time duration 30 seconds
|
64
|
-
fresh.fincr "recent_posts:#{user.id}"
|
65
|
-
|
66
|
-
# ...
|
88
|
+
fresh.fincr "recent_posts:#{user.id}"
|
67
89
|
|
68
|
-
#
|
69
|
-
fresh.fsum "recent_posts:#{user.id}"
|
90
|
+
# do stuff
|
91
|
+
fresh.fsum "recent_posts:#{user.id}"
|
70
92
|
```
|
71
93
|
|
72
94
|
# Recipes
|
data/fresh_redis.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.add_runtime_dependency 'redis'
|
19
19
|
|
20
20
|
gem.add_development_dependency 'rspec'
|
21
|
-
gem.add_development_dependency 'mock_redis', '0.5.
|
21
|
+
gem.add_development_dependency 'mock_redis', '0.5.4'
|
22
22
|
gem.add_development_dependency 'guard-rspec', '2.1.0'
|
23
23
|
gem.add_development_dependency 'rake', '0.9.2.2'
|
24
24
|
gem.add_development_dependency 'timecop', '0.5.2'
|
data/lib/fresh_redis/key.rb
CHANGED
data/lib/fresh_redis/string.rb
CHANGED
@@ -1,13 +1,33 @@
|
|
1
1
|
class FreshRedis
|
2
2
|
module String
|
3
|
+
def fincrby(key, increment, options={})
|
4
|
+
key = build_key(key, options)
|
5
|
+
@redis.multi do
|
6
|
+
@redis.incrby(key.redis_key, increment)
|
7
|
+
@redis.expire(key.redis_key, key.freshness)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
3
11
|
def fincr(key, options={})
|
12
|
+
fincrby(key, 1, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def fincrbyfloat(key, increment, options={})
|
4
16
|
key = build_key(key, options)
|
5
17
|
@redis.multi do
|
6
|
-
@redis.
|
18
|
+
@redis.incrbyfloat(key.redis_key, increment)
|
7
19
|
@redis.expire(key.redis_key, key.freshness)
|
8
20
|
end
|
9
21
|
end
|
10
22
|
|
23
|
+
def fdecr(key, options={})
|
24
|
+
fincrby(key, -1, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def fdecrby(key, decrement, options={})
|
28
|
+
fincrby(key, -1 * decrement, options)
|
29
|
+
end
|
30
|
+
|
11
31
|
def fsum(key, options={})
|
12
32
|
key = build_key(key, options)
|
13
33
|
@redis.pipelined {
|
@@ -15,7 +35,7 @@ class FreshRedis
|
|
15
35
|
@redis.get(bucket_key)
|
16
36
|
end
|
17
37
|
}.reduce(0){|acc, value|
|
18
|
-
value ? acc + value.
|
38
|
+
value ? acc + value.to_f : acc
|
19
39
|
}
|
20
40
|
end
|
21
41
|
end
|
data/lib/fresh_redis/version.rb
CHANGED
@@ -7,6 +7,7 @@ describe FreshRedis do
|
|
7
7
|
let(:mock_redis) { MockRedis.new }
|
8
8
|
let(:now) { Time.new(2012, 9, 27, 15, 40, 56, "+10:00") }
|
9
9
|
let(:normalized_now_minute) { Time.new(2012, 9, 27, 15, 40, 0, "+10:00") }
|
10
|
+
let(:normalized_one_minute_ago) { Time.new(2012, 9, 27, 15, 39, 0, "+10:00") }
|
10
11
|
|
11
12
|
before(:each) { Timecop.travel(now) }
|
12
13
|
after(:each) { Timecop.return }
|
@@ -16,8 +17,9 @@ describe FreshRedis do
|
|
16
17
|
it "should increment the key for the normalized timestamp" do
|
17
18
|
Timecop.freeze(now) { subject.fincr "foo" }
|
18
19
|
Timecop.freeze(now + 3) { subject.fincr "foo" }
|
19
|
-
Timecop.freeze(now
|
20
|
+
Timecop.freeze(now - 60) { subject.fincr "foo" } # different normalized key
|
20
21
|
mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_i.should == 2
|
22
|
+
mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_i.should == 1
|
21
23
|
end
|
22
24
|
|
23
25
|
it "should set the freshness as the expiry" do
|
@@ -27,21 +29,75 @@ describe FreshRedis do
|
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
32
|
+
describe "#fincrby" do
|
33
|
+
it "should increment the key for the normalized timestamp by the specified amount" do
|
34
|
+
Timecop.freeze(now) { subject.fincrby "foo", 2 }
|
35
|
+
Timecop.freeze(now + 3) { subject.fincrby "foo", 3 }
|
36
|
+
Timecop.freeze(now - 60) { subject.fincrby "foo", 4 } # different normalized key
|
37
|
+
mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_i.should == 5
|
38
|
+
mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_i.should == 4
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should set the freshness as the expiry" do
|
42
|
+
# relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
|
43
|
+
subject.fincrby "foo", 5, :freshness => 3600
|
44
|
+
mock_redis.ttl("foo:#{normalized_now_minute.to_i}").should == 3600
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#fincrbyfloat" do
|
49
|
+
it "should increment the key for the normalized timestamp by the specified amount" do
|
50
|
+
Timecop.freeze(now) { subject.fincrbyfloat "foo", 2.1 }
|
51
|
+
Timecop.freeze(now + 3) { subject.fincrbyfloat "foo", 3.2 }
|
52
|
+
Timecop.freeze(now - 60) { subject.fincrbyfloat "foo", 4.3 } # different normalized key
|
53
|
+
mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_f.should be_within(0.00001).of(5.3)
|
54
|
+
mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_f.should be_within(0.00001).of(4.3)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should set the freshness as the expiry" do
|
58
|
+
# relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
|
59
|
+
subject.fincrbyfloat "foo", 5, :freshness => 3600
|
60
|
+
mock_redis.ttl("foo:#{normalized_now_minute.to_i}").should == 3600
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#fdecr" do
|
65
|
+
it "should decrement the key for the normalized timestamp" do
|
66
|
+
Timecop.freeze(now) { subject.fdecr "foo" }
|
67
|
+
Timecop.freeze(now + 3) { subject.fdecr "foo" }
|
68
|
+
Timecop.freeze(now - 60) { subject.fdecr "foo" } # different normalized key
|
69
|
+
mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_i.should == -2
|
70
|
+
mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_i.should == -1
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should set the freshness as the expiry" do
|
74
|
+
# relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
|
75
|
+
subject.fdecr "foo", :freshness => 3600
|
76
|
+
mock_redis.ttl("foo:#{normalized_now_minute.to_i}").should == 3600
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#fdecrby" do
|
81
|
+
it "should decrement the key for the normalized timestamp by the specified amount" do
|
82
|
+
Timecop.freeze(now) { subject.fdecrby "foo", 2 }
|
83
|
+
Timecop.freeze(now + 3) { subject.fdecrby "foo", 3 }
|
84
|
+
Timecop.freeze(now - 60) { subject.fdecrby "foo", 4 } # different normalized key
|
85
|
+
mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_i.should == -5
|
86
|
+
mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_i.should == -4
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should set the freshness as the expiry" do
|
90
|
+
# relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
|
91
|
+
subject.fdecrby "foo", 5, :freshness => 3600
|
92
|
+
mock_redis.ttl("foo:#{normalized_now_minute.to_i}").should == 3600
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
30
96
|
describe "#fsum" do
|
31
|
-
subject{ FreshRedis.new(mock_redis, :granularity => 10, :freshness => 60) }
|
32
97
|
it "should add the values of keys for specified freshness and granularity" do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
Timecop.freeze(now - 60 + 3) { subject.fincr "foo" }
|
37
|
-
Timecop.freeze(now - 60 + 5) { subject.fincr "foo" }
|
38
|
-
Timecop.freeze(now - 60 + 8) { subject.fincr "foo" }
|
39
|
-
Timecop.freeze(now - 60 + 13) { subject.fincr "foo" }
|
40
|
-
Timecop.freeze(now - 60 + 21) { subject.fincr "foo" }
|
41
|
-
Timecop.freeze(now - 60 + 34) { subject.fincr "foo" }
|
42
|
-
Timecop.freeze(now - 60 + 55) { subject.fincr "foo" }
|
43
|
-
|
44
|
-
subject.fsum("foo").should == 9
|
98
|
+
mock_redis.set("foo:#{normalized_now_minute.to_i}", "7")
|
99
|
+
mock_redis.set("foo:#{normalized_one_minute_ago.to_i}", "-2")
|
100
|
+
subject.fsum("foo").should == 5
|
45
101
|
end
|
46
102
|
end
|
47
103
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fresh_redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
requirements:
|
51
51
|
- - '='
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.5.
|
53
|
+
version: 0.5.4
|
54
54
|
type: :development
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - '='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.5.
|
61
|
+
version: 0.5.4
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: guard-rspec
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -146,7 +146,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
146
146
|
version: '0'
|
147
147
|
segments:
|
148
148
|
- 0
|
149
|
-
hash:
|
149
|
+
hash: 3765390037678342565
|
150
150
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
151
|
none: false
|
152
152
|
requirements:
|
@@ -155,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
155
|
version: '0'
|
156
156
|
segments:
|
157
157
|
- 0
|
158
|
-
hash:
|
158
|
+
hash: 3765390037678342565
|
159
159
|
requirements: []
|
160
160
|
rubyforge_project:
|
161
161
|
rubygems_version: 1.8.23
|