raidis 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/bukowskis/raidis.png)](https://travis-ci.org/bukowskis/raidis)
2
+
1
3
  # Raid-is
2
4
 
3
5
  Raidis is yet another failover solution for Redis.
@@ -27,7 +29,7 @@ Raidis.redis.class # => Redis::Namespace
27
29
 
28
30
  # How does it work?
29
31
 
30
- Whenever you call `Raidis.redis`, the connectivity to the remote redis server is monitored. If connectivity problems occur, or you're trying to make write-calls to a Redis slave, Raidis will immediately reload the `/etc/redis_master` file and try to perform the call to Redis again (hoping to have connected to a working redis server this time). If that second attempt failed (or you set `config.retries` to `0`) a `Raidis::ConnectionError` is raised.
32
+ Whenever you call `Raidis.redis`, the connectivity to the remote redis server is monitored. If connectivity problems occur, or you're trying to make write-calls to a Redis slave, Raidis will immediately reload the `/etc/redis_master` file and try to perform the call to Redis again (hoping to have connected to a working redis server this time). If that second attempt failed (or you set `config.retries` to `0`) a `Raidis::ConnectionError` is raised. To prevent too many retries in too short time, there is a minimum interval that Radis will wait after every retry attempt (see config `retry_interval`, default is 3 seconds).
31
33
 
32
34
  You should not have too many retries, because you don't want the end user's browser to hang and wait too long. That's where the `#available?` feature comes in. As soon as one of those connection errors occurs, the global variable `Raidis.available?` turns from `true` to `false` and any further damage can be mitigated, by simply not making any further calls to redis. You should inform your end-users about the outage in a friendly manner. E.g. like so:
33
35
 
@@ -60,6 +62,7 @@ Raidis.configure do |config|
60
62
  config.redis_timeout = 3 # seconds # default is whatever Redis.new has as default
61
63
 
62
64
  config.retries = 3 # times # default is 1
65
+ config.retry_interval = 3 # seconds # default is 3
63
66
  config.unavailability_timeout = 60 # seconds # default is 15 seconds
64
67
  config.info_file_path = '/opt/redis_server' # default is '/etc/redis_master'
65
68
 
@@ -13,7 +13,7 @@ module Raidis
13
13
  end
14
14
 
15
15
  attr_accessor :redis_namespace, :redis_timeout, :redis_db
16
- attr_writer :logger, :unavailability_timeout, :master, :retries
16
+ attr_writer :logger, :unavailability_timeout, :master, :retries, :retry_interval
17
17
 
18
18
  def logger
19
19
  @logger ||= begin
@@ -41,6 +41,10 @@ module Raidis
41
41
  @retries ||= 1
42
42
  end
43
43
 
44
+ def retry_interval
45
+ @retry_interval ||= 3
46
+ end
47
+
44
48
  def master
45
49
  unless @master
46
50
  unless info_file_path.exist?
@@ -1,3 +1,5 @@
1
+ require 'raidis/throttle'
2
+
1
3
  module Raidis
2
4
  class RedisWrapper
3
5
 
@@ -24,7 +26,9 @@ module Raidis
24
26
  result = block.call
25
27
  rescue Raidis::ConnectionError => exception
26
28
  # Try again a couple of times.
29
+ throttle.sleep_if_needed
27
30
  if (tries -= 1) >= 0
31
+ throttle.action!
28
32
  reconnect!
29
33
  retry
30
34
  end
@@ -81,6 +85,10 @@ module Raidis
81
85
  @redis = nil
82
86
  end
83
87
 
88
+ def throttle
89
+ @throttle ||= Throttle.new config.retry_interval
90
+ end
91
+
84
92
  # Internal: Establishes a brand-new, raw connection to Redis.
85
93
  #
86
94
  # Returns a Redis::Namespace instance or nil if we don't know where the Redis server is.
@@ -0,0 +1,22 @@
1
+ module Raidis
2
+ class Throttle
3
+
4
+ attr_reader :interval, :last_action
5
+
6
+ def initialize(seconds = 3)
7
+ @interval = seconds.to_i
8
+ end
9
+
10
+ def sleep_if_needed
11
+ return unless last_action
12
+ duration = last_action + interval - Time.now
13
+ return unless duration.to_i > 0
14
+ sleep duration
15
+ end
16
+
17
+ def action!
18
+ @last_action = Time.now
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Raidis::Throttle do
4
+
5
+ let(:interval) { 3 }
6
+ let(:throttle) { Raidis::Throttle.new }
7
+
8
+ before do
9
+ Raidis::Throttle.stub!(:sleep)
10
+ end
11
+
12
+ context 'no action took place yet' do
13
+ describe '#sleep_if_needed' do
14
+ it 'does not sleep' do
15
+ throttle.should_not_receive(:sleep)
16
+ throttle.sleep_if_needed
17
+ end
18
+ end
19
+ end
20
+
21
+ context 'action took place 1 second ago' do
22
+ before do
23
+ Timecop.freeze
24
+ throttle.action!
25
+ Timecop.freeze Time.now + 1
26
+ end
27
+
28
+ describe '#sleep_if_needed' do
29
+ it 'sleeps for 2 seconds' do
30
+ throttle.should_receive(:sleep).with(2)
31
+ throttle.sleep_if_needed
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'action took place 2 seconds ago' do
37
+ before do
38
+ Timecop.freeze
39
+ throttle.action!
40
+ Timecop.freeze Time.now + 2
41
+ end
42
+
43
+ describe '#sleep_if_needed' do
44
+ it 'sleeps for 1 seconds' do
45
+ throttle.should_receive(:sleep).with(1)
46
+ throttle.sleep_if_needed
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'action took place 4 seconds ago' do
52
+ before do
53
+ Timecop.freeze
54
+ throttle.action!
55
+ Timecop.freeze Time.now + 4
56
+ end
57
+
58
+ describe '#sleep_if_needed' do
59
+ it 'does not sleep' do
60
+ throttle.should_not_receive(:sleep)
61
+ throttle.sleep_if_needed
62
+ end
63
+ end
64
+ end
65
+
66
+ end
@@ -21,7 +21,7 @@ end
21
21
 
22
22
  describe Raidis::RedisWrapper do
23
23
 
24
- let(:config) { mock(:config, retries: 3) }
24
+ let(:config) { mock(:config, retries: 3, retry_interval: 0) }
25
25
  let(:backend) { mock(:backend) }
26
26
  let(:shaky_backend) { ShakyRedis.new }
27
27
  let(:wrapper) { Raidis::RedisWrapper.new }
@@ -71,13 +71,27 @@ describe Raidis::RedisWrapper do
71
71
  wrapper.some_redis_command.should == :shaky_result
72
72
  end
73
73
 
74
- context 'when running out of retries' do
74
+ context 'with a retry interval' do
75
75
  before do
76
- config.stub!(:retries).and_return 2
76
+ config.stub!(:retry_interval).and_return 10
77
77
  end
78
78
 
79
- it 'finally raises a unified connection error' do
80
- expect { wrapper.some_redis_command }.to raise_error(Raidis::ConnectionError)
79
+ it 'waits some time before each retry' do
80
+ wrapper.throttle.should_receive(:sleep).exactly(2).times.with(10)
81
+ Timecop.freeze
82
+ wrapper.some_redis_command.should == :shaky_result
83
+ end
84
+
85
+ context 'when running out of retries' do
86
+ before do
87
+ config.stub!(:retries).and_return 1
88
+ end
89
+
90
+ it 'finally raises a unified connection error' do
91
+ wrapper.throttle.should_receive(:sleep).with(10)
92
+ Timecop.freeze
93
+ expect { wrapper.some_redis_command }.to raise_error(Raidis::ConnectionError)
94
+ end
81
95
  end
82
96
  end
83
97
  end
@@ -34,6 +34,7 @@ RSpec.configure do |config|
34
34
  Raidis.configure do |config|
35
35
  config.redis_db = 15
36
36
  config.redis_timeout = 0.5
37
+ config.retry_interval = 0
37
38
  end
38
39
  Raidis.config.stub!(:info_file_path).and_return mock(:info_file_path, exist?: true, readable?: true, read: '127.0.0.1')
39
40
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raidis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -100,7 +100,9 @@ files:
100
100
  - lib/raidis/availability.rb
101
101
  - lib/raidis/configuration.rb
102
102
  - lib/raidis/redis_wrapper.rb
103
+ - lib/raidis/throttle.rb
103
104
  - lib/raidis.rb
105
+ - spec/lib/raidis/throttle_spec.rb
104
106
  - spec/lib/raidis_spec.rb
105
107
  - spec/lib/redis_wrapper_spec.rb
106
108
  - spec/spec_helper.rb