sequel-replica-failover 0.1.2 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.md +2 -0
- data/lib/sequel-replica-failover/version.rb +1 -1
- data/lib/sequel/connection_pool/sharded_single_failover.rb +38 -14
- data/spec/sequel-replica-failover/dtrace_provider_spec.rb +1 -1
- data/spec/sequel/connection_pool/sharded_single_failover_spec.rb +68 -37
- data/spec/spec_helper.rb +0 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4427d0bd15392df069157501d649dc8f1208ca14
|
4
|
+
data.tar.gz: bc0ee4ad2bcad88f2aaf3d724418dea7b5d47c82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80086a61fbc3969a6696df41f25d4b97f9306528c502d0cfe9c289dc5e18dbc0a25f69de1863fe18fa1ea161148918d6aae0dc61ccf89b5236735e93ef5e230c
|
7
|
+
data.tar.gz: 473f829e1df49607fc6516fb5b3f583b820b12ba878f7057b08b0e4b43976a7112c1a464d96bffd2b6e5ba49549bba68ec32f386306a9109f6e3fbbb2e52eb7c
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Automatic read-only failover for Sequel
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/sequel-replica-failover)
|
4
|
+
[](https://travis-ci.org/wanelo/sequel-replica-failover)
|
4
6
|
[](https://codeclimate.com/github/wanelo/sequel-replica-failover)
|
5
7
|
|
6
8
|
This provides a NOT-THREADSAFE sharded connection pool for failing over between configured replicas.
|
@@ -8,10 +8,30 @@ class Sequel::ShardedSingleFailoverConnectionPool < Sequel::ShardedSingleConnect
|
|
8
8
|
@pool_retry_count = opts[:pool_retry_count] || 5
|
9
9
|
end
|
10
10
|
|
11
|
+
@on_disconnect = []
|
12
|
+
@on_unstick = []
|
13
|
+
|
11
14
|
class << self
|
12
|
-
attr_accessor :on_disconnect
|
15
|
+
attr_accessor :on_disconnect, :on_unstick
|
16
|
+
|
17
|
+
def register_on_disconnect_callback(callback)
|
18
|
+
@on_disconnect << callback
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear_on_disconnect_callbacks
|
22
|
+
@on_disconnect.clear
|
23
|
+
end
|
24
|
+
|
25
|
+
def register_on_unstick_callback(callback)
|
26
|
+
@on_unstick << callback
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear_on_unstick_callbacks
|
30
|
+
@on_unstick.clear
|
31
|
+
end
|
13
32
|
end
|
14
33
|
|
34
|
+
|
15
35
|
# Yields the connection to the supplied block for the given server.
|
16
36
|
# This method simulates the ConnectionPool#hold API.
|
17
37
|
def hold(server=:default, &block)
|
@@ -23,31 +43,33 @@ class Sequel::ShardedSingleFailoverConnectionPool < Sequel::ShardedSingleConnect
|
|
23
43
|
|
24
44
|
super(server, &block)
|
25
45
|
rescue Sequel::DatabaseDisconnectError, Sequel::DatabaseConnectionError => e
|
26
|
-
if server
|
27
|
-
|
28
|
-
disconnect_server(server)
|
29
|
-
@conns[server] = nil
|
46
|
+
raise if server != :read_only
|
47
|
+
raise if @db.in_transaction?(server: :read_only)
|
30
48
|
|
31
|
-
|
49
|
+
unless self.class.on_disconnect.empty?
|
50
|
+
self.class.on_disconnect.each { |callback| callback.call(e, self) }
|
51
|
+
end
|
52
|
+
disconnect_server(server)
|
53
|
+
@conns[server] = nil
|
32
54
|
|
33
|
-
|
34
|
-
unstick(server)
|
35
|
-
raise
|
36
|
-
end
|
55
|
+
stick
|
37
56
|
|
38
|
-
|
39
|
-
|
57
|
+
if @stuck_times >= @pool_retry_count
|
58
|
+
unstick(server)
|
40
59
|
raise
|
41
60
|
end
|
61
|
+
|
62
|
+
hold(server, &block)
|
42
63
|
end
|
43
64
|
|
44
65
|
def pool_type
|
45
66
|
:sharded_single_failover
|
46
67
|
end
|
47
68
|
|
48
|
-
private
|
49
|
-
|
50
69
|
def unstick(server)
|
70
|
+
unless self.class.on_unstick.empty?
|
71
|
+
self.class.on_unstick.each { |callback| callback.call(self) }
|
72
|
+
end
|
51
73
|
probe(server.to_s) { |p| p.unstick }
|
52
74
|
disconnect_server(server)
|
53
75
|
@conns[server] = nil
|
@@ -55,6 +77,8 @@ class Sequel::ShardedSingleFailoverConnectionPool < Sequel::ShardedSingleConnect
|
|
55
77
|
@stuck_times = nil
|
56
78
|
end
|
57
79
|
|
80
|
+
private
|
81
|
+
|
58
82
|
def stick
|
59
83
|
@stuck_times ||= 0
|
60
84
|
probe(@stuck_times) { |p| p.stick }
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Sequel::ReplicaFailover::DTraceProvider do
|
4
4
|
describe 'initialize' do
|
5
5
|
it 'creates a new provider' do
|
6
|
-
USDT::Provider.
|
6
|
+
expect(USDT::Provider).to receive(:create).with(:ruby, :sequel_replica_failover)
|
7
7
|
Sequel::ReplicaFailover::DTraceProvider.new
|
8
8
|
end
|
9
9
|
end
|
@@ -1,88 +1,119 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
CONNECTION_POOL_DEFAULTS = {:pool_retry_count => 3,
|
3
|
-
:pool_stick_timeout => 15,
|
4
|
-
:pool_timeout=>5,
|
5
|
-
:pool_sleep_time=>0.001,
|
6
|
-
:max_connections=>4,
|
7
|
-
:pool_class => Sequel::ShardedSingleFailoverConnectionPool,
|
8
|
-
:servers => { :read_only => {} } }
|
9
|
-
|
10
|
-
mock_db = lambda do |*a, &b|
|
11
|
-
db = Sequel.mock
|
12
|
-
(class << db; self end).send(:define_method, :connect){|c| b.arity == 1 ? b.call(c) : b.call} if b
|
13
|
-
if b2 = a.shift
|
14
|
-
(class << db; self end).send(:define_method, :disconnect_connection){|c| b2.arity == 1 ? b2.call(c) : b2.call}
|
15
|
-
end
|
16
|
-
db
|
17
|
-
end
|
18
2
|
|
19
3
|
describe Sequel::ShardedSingleFailoverConnectionPool do
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
4
|
+
CONNECTION_POOL_DEFAULTS = {:pool_retry_count => 3,
|
5
|
+
:pool_stick_timeout => 15,
|
6
|
+
:pool_timeout=>5,
|
7
|
+
:pool_sleep_time=>0.001,
|
8
|
+
:max_connections=>4,
|
9
|
+
:pool_class => Sequel::ShardedSingleFailoverConnectionPool,
|
10
|
+
:servers => { :read_only => {} } }
|
11
|
+
|
12
|
+
mock_db = lambda do |*a, &b|
|
13
|
+
db = Sequel.mock
|
14
|
+
(class << db; self end).send(:define_method, :connect){|c| b.arity == 1 ? b.call(c) : b.call} if b
|
15
|
+
if b2 = a.shift
|
16
|
+
(class << db; self end).send(:define_method, :disconnect_connection){|c| b2.arity == 1 ? b2.call(c) : b2.call}
|
24
17
|
end
|
18
|
+
db
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
Sequel::ShardedSingleFailoverConnectionPool.clear_on_disconnect_callbacks
|
23
|
+
Sequel::ShardedSingleFailoverConnectionPool.clear_on_unstick_callbacks
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:msp) { proc { @max_size=3 } }
|
27
|
+
let(:connection_pool) { Sequel::ConnectionPool.get_pool(mock_db.call(proc { |c| msp.call }) { :got_connection }, CONNECTION_POOL_DEFAULTS) }
|
25
28
|
|
29
|
+
describe '#hold' do
|
26
30
|
context 'with read_only server' do
|
27
31
|
context 'when block raises a database connection error' do
|
28
32
|
it 'retries until the a successful connection is made' do
|
29
33
|
call_count = 0
|
30
|
-
|
34
|
+
expect {
|
35
|
+
connection_pool.hold(:read_only) { call_count += 1; raise Sequel::DatabaseDisconnectError if call_count == 1 }
|
36
|
+
}.not_to raise_error
|
31
37
|
expect(call_count).to eq(2)
|
32
38
|
end
|
33
39
|
|
34
40
|
it 'only retries N number of times before actually raising the error' do
|
35
41
|
call_count = 0
|
36
|
-
|
42
|
+
expect {
|
43
|
+
connection_pool.hold(:read_only) { call_count += 1; raise Sequel::DatabaseDisconnectError }
|
44
|
+
}.to raise_error(Sequel::DatabaseDisconnectError)
|
37
45
|
expect(call_count).to eq(3)
|
38
46
|
end
|
39
47
|
|
40
48
|
it 'sticks for N number of seconds to a working connection' do
|
41
49
|
Timecop.freeze -16 do
|
42
50
|
call_count = 0
|
43
|
-
|
51
|
+
expect {
|
52
|
+
connection_pool.hold(:read_only) { call_count += 1; raise Sequel::DatabaseDisconnectError if call_count == 1 }
|
53
|
+
}.not_to raise_error
|
44
54
|
expect(call_count).to eq(2)
|
45
|
-
expect(
|
55
|
+
expect(connection_pool.size).to eq(1)
|
46
56
|
end
|
47
57
|
|
48
|
-
|
49
|
-
|
58
|
+
expect(connection_pool).to receive(:make_new).once
|
59
|
+
connection_pool.hold(:read_only) {}
|
50
60
|
end
|
51
61
|
|
52
62
|
context 'with an on_disconnect callback' do
|
53
63
|
it 'calls the callback with the error' do
|
54
64
|
callback = double("callback")
|
55
|
-
Sequel::ShardedSingleFailoverConnectionPool.
|
65
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_disconnect_callback callback
|
56
66
|
|
57
|
-
expect(callback).to receive(:call).with(an_instance_of(Sequel::DatabaseDisconnectError),
|
67
|
+
expect(callback).to receive(:call).with(an_instance_of(Sequel::DatabaseDisconnectError), connection_pool)
|
58
68
|
call_count = 0
|
59
|
-
|
69
|
+
connection_pool.hold(:read_only) { call_count += 1; raise Sequel::DatabaseDisconnectError if call_count == 1 }
|
60
70
|
end
|
61
71
|
end
|
62
72
|
|
63
73
|
context 'when in a transaction' do
|
64
74
|
it 'raises an exception' do
|
65
|
-
Sequel::Mock::Database.
|
66
|
-
|
75
|
+
allow_any_instance_of(Sequel::Mock::Database).to receive(:in_transaction?).and_return(true)
|
76
|
+
expect {
|
77
|
+
connection_pool.hold(:read_only) { raise Sequel::DatabaseDisconnectError }
|
78
|
+
}.to raise_error(Sequel::DatabaseDisconnectError)
|
67
79
|
end
|
68
80
|
end
|
69
81
|
end
|
70
82
|
end
|
71
83
|
|
72
|
-
context 'with default or
|
84
|
+
context 'with default or arbitrary server' do
|
73
85
|
it 'does no retry logic and raises error' do
|
74
86
|
call_count = 0
|
75
|
-
|
87
|
+
expect {
|
88
|
+
connection_pool.hold { call_count += 1; raise Sequel::DatabaseDisconnectError if call_count == 1 }
|
89
|
+
}.to raise_error(Sequel::DatabaseDisconnectError)
|
76
90
|
expect(call_count).to eq(1)
|
77
91
|
end
|
78
92
|
end
|
79
93
|
end
|
80
94
|
|
81
|
-
describe '
|
82
|
-
it '
|
95
|
+
describe '#unstick' do
|
96
|
+
it 'calls on_unstick callbacks' do
|
97
|
+
callback = double(call: true)
|
98
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_unstick_callback callback
|
99
|
+
connection_pool.unstick(:read_only)
|
100
|
+
expect(callback).to have_received(:call).with(connection_pool)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '.register_on_disconnect_callback' do
|
105
|
+
it 'adds to the on_disconnect attribute' do
|
106
|
+
callback = Proc.new{ puts "woo" }
|
107
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_disconnect_callback callback
|
108
|
+
expect(Sequel::ShardedSingleFailoverConnectionPool.on_disconnect).to eq([callback])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '.register_on_disconnect_callback' do
|
113
|
+
it 'adds to the on_unstick attribute' do
|
83
114
|
callback = Proc.new{ puts "woo" }
|
84
|
-
Sequel::ShardedSingleFailoverConnectionPool.
|
85
|
-
expect(Sequel::ShardedSingleFailoverConnectionPool.
|
115
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_unstick_callback callback
|
116
|
+
expect(Sequel::ShardedSingleFailoverConnectionPool.on_unstick).to eq([callback])
|
86
117
|
end
|
87
118
|
end
|
88
119
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel-replica-failover
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Henry
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -151,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
151
|
version: '0'
|
152
152
|
requirements: []
|
153
153
|
rubyforge_project:
|
154
|
-
rubygems_version: 2.2.
|
154
|
+
rubygems_version: 2.2.2
|
155
155
|
signing_key:
|
156
156
|
specification_version: 4
|
157
157
|
summary: Automatically failover when replicas go down.
|