Merge branch 'key-expiry'

This commit is contained in:
2012-04-18 16:43:22 +01:00
8 changed files with 82 additions and 11 deletions

View File

@@ -1,5 +1,8 @@
language: ruby
rvm:
- 1.8.7
- 1.9.2
- jruby
- 1.9.3
- jruby-18mode
- jruby-19mode
- ree

View File

@@ -202,6 +202,36 @@ This section does need further expanding as there's a lot to cover when it
comes to the finder.
## Key Expiry
Support for expiring keys from Redis is available, allowing you too keep
varying levels of details for X period of time. This allows you easily keep
things nice and tidy by only storing varying levels detailed stats only for as
long as you need.
In the below example we define how long Redis keys for varying depths are
stored. Second by second stats are available for 10 minutes, minute by minute
stats for 6 hours, hourly stats for 3 months, daily stats for 2 years, and
yearly stats are retained forever.
```ruby
class ViewStats
include Redistat::Model
depth :sec
expire \
:sec => 10.minutes.to_i,
:min => 6.hours.to_i,
:hour => 3.months.to_i,
:day => 2.years.to_i
end
```
Keep in mind that when storing stats for a custom date in the past for
example, the expiry time for the keys will be relative to now. The values you
specify are simply passed to the `Redis#expire` method.
## Internals

View File

@@ -4,6 +4,7 @@ module Redistat
module Connection
REQUIRED_SERVER_VERSION = "1.3.10"
MIN_EXPIRE_SERVER_VERSION = "2.1.3"
# TODO: Create a ConnectionPool instance object using Sychronize mixin to replace Connection class
@@ -67,6 +68,10 @@ module Redistat
def check_redis_version(conn)
raise RedisServerIsTooOld if conn.info["redis_version"] < REQUIRED_SERVER_VERSION
if conn.info["redis_version"] < MIN_EXPIRE_SERVER_VERSION
STDOUT.puts "WARNING: You MUST upgrade Redis to v2.1.3 or later " +
"if you are using key expiry."
end
conn
end

View File

@@ -46,6 +46,14 @@ module Redistat
alias :class_name :scope
def expire(exp = nil)
if !exp.nil?
options[:expire] = exp.is_a?(Hash) ? exp : Hash.new(exp)
else
options[:expire]
end
end
def connect_to(opts = {})
Connection.create(opts.merge(:ref => name))
options[:connection_ref] = name

View File

@@ -5,9 +5,12 @@ module Redistat
class << self
def default_options
{ :enable_grouping => true,
:label_indexing => true,
:connection_ref => nil }
{
:enable_grouping => true,
:label_indexing => true,
:connection_ref => nil,
:expire => {}
}
end
def buffer
@@ -29,30 +32,34 @@ module Redistat
update(*args) unless buffer.store(*args)
end
def update(key, stats, depth_limit, opts)
def update(key, stats, depth_limit, opts = {})
if opts[:enable_grouping]
stats = inject_group_summaries(stats)
key.groups.each do |k|
update_key(k, stats, depth_limit, opts[:connection_ref])
update_key(k, stats, depth_limit, opts)
k.update_index if opts[:label_indexing]
end
else
update_key(key, stats, depth_limit, opts[:connection_ref])
update_key(key, stats, depth_limit, opts)
end
end
private
def update_key(key, stats, depth_limit, connection_ref)
def update_key(key, stats, depth_limit, opts = {})
Date::DEPTHS.each do |depth|
update_fields(key, stats, depth, connection_ref)
update_fields(key, stats, depth, opts)
break if depth == depth_limit
end
end
def update_fields(key, stats, depth, connection_ref = nil)
def update_fields(key, stats, depth, opts = {})
stats.each do |field, value|
db(connection_ref).hincrby key.to_s(depth), field, value
db(opts[:connection_ref]).hincrby key.to_s(depth), field, value
end
if opts[:expire] && !opts[:expire][depth].nil?
db(opts[:connection_ref]).expire key.to_s(depth), opts[:expire][depth]
end
end

View File

@@ -26,5 +26,6 @@ class ModelHelper4
include Redistat::Model
scope "FancyHelper"
expire :hour => 24*3600
end

View File

@@ -38,6 +38,7 @@ describe Redistat::Model do
ModelHelper2.store_event.should == true
ModelHelper2.hashed_label.should == true
ModelHelper2.scope.should be_nil
ModelHelper2.expire.should be_nil
ModelHelper1.depth.should == nil
ModelHelper1.store_event.should == nil
@@ -57,6 +58,7 @@ describe Redistat::Model do
ModelHelper4.scope.should == "FancyHelper"
ModelHelper4.send(:name).should == "FancyHelper"
ModelHelper4.expire.should == {:hour => 24*3600}
end
it "should store and fetch stats" do

View File

@@ -10,6 +10,7 @@ describe Redistat::Summary do
@date = Time.now
@key = Redistat::Key.new(@scope, @label, @date, {:depth => :day})
@stats = {"views" => 3, "visitors" => 2}
@expire = {:hour => 24*3600}
end
it "should update a single summary properly" do
@@ -32,6 +33,20 @@ describe Redistat::Summary do
summary["visitors"].should == "1"
end
it "should set key expiry properly" do
Redistat::Summary.update_all(@key, @stats, :hour,{:expire => @expire})
((24*3600)-1..(24*3600)+1).should include(db.ttl(@key.to_s(:hour)))
[:day, :month, :year].each do |depth|
db.ttl(@key.to_s(depth)).should == -1
end
db.flushdb
Redistat::Summary.update_all(@key, @stats, :hour, {:expire => {}})
[:hour, :day, :month, :year].each do |depth|
db.ttl(@key.to_s(depth)).should == -1
end
end
it "should update all summaries properly" do
Redistat::Summary.update_all(@key, @stats, :sec)
[:year, :month, :day, :hour, :min, :sec, :usec].each do |depth|