master_slave_adapter 0.2.0 → 1.0.0.beta1
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/.travis.yml +1 -0
- data/CHANGELOG.md +11 -0
- data/TODO.txt +14 -4
- data/lib/active_record/connection_adapters/master_slave_adapter/circuit_breaker.rb +61 -0
- data/lib/active_record/connection_adapters/master_slave_adapter/clock.rb +42 -0
- data/lib/active_record/connection_adapters/master_slave_adapter/version.rb +7 -0
- data/lib/active_record/connection_adapters/master_slave_adapter.rb +501 -1
- data/lib/active_record/connection_adapters/mysql_master_slave_adapter.rb +72 -1
- data/lib/master_slave_adapter.rb +1 -624
- data/master_slave_adapter.gemspec +8 -6
- data/spec/circuit_breaker_spec.rb +52 -0
- data/spec/master_slave_adapter_spec.rb +48 -293
- data/spec/mysql_master_slave_adapter_spec.rb +320 -0
- metadata +16 -8
- data/VERSION +0 -1
@@ -0,0 +1,320 @@
|
|
1
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
class Mysql
|
6
|
+
class Error
|
7
|
+
CR_CONNECTION_ERROR = 1
|
8
|
+
CR_CONN_HOST_ERROR = 2
|
9
|
+
CR_SERVER_GONE_ERROR = 4
|
10
|
+
CR_SERVER_LOST = 8
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'active_record/connection_adapters/mysql_master_slave_adapter'
|
15
|
+
|
16
|
+
ActiveRecord::Base.logger =
|
17
|
+
Logger.new($stdout).tap { |l| l.level = Logger::DEBUG }
|
18
|
+
|
19
|
+
module ActiveRecord
|
20
|
+
class Base
|
21
|
+
cattr_accessor :master_mock, :slave_mock
|
22
|
+
def self.mysql_connection(config)
|
23
|
+
config[:database] == 'slave' ? slave_mock : master_mock
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
|
29
|
+
let(:default_database_setup) do
|
30
|
+
{
|
31
|
+
:adapter => 'master_slave',
|
32
|
+
:username => 'root',
|
33
|
+
:database => 'slave',
|
34
|
+
:connection_adapter => 'mysql',
|
35
|
+
:master => { :username => 'root', :database => 'master' },
|
36
|
+
:slaves => [{ :database => 'slave' }],
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
let(:database_setup) { default_database_setup }
|
41
|
+
|
42
|
+
let(:mocked_methods) do
|
43
|
+
{
|
44
|
+
:reconnect! => true,
|
45
|
+
:disconnect! => true,
|
46
|
+
:active? => true,
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
let!(:master_connection) do
|
51
|
+
mock(
|
52
|
+
'master connection',
|
53
|
+
mocked_methods.merge(:open_transactions => 0)
|
54
|
+
).tap do |conn|
|
55
|
+
conn.stub!(:uncached).and_yield
|
56
|
+
ActiveRecord::Base.master_mock = conn
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
let!(:slave_connection) do
|
61
|
+
mock('slave connection', mocked_methods).tap do |conn|
|
62
|
+
conn.stub!(:uncached).and_yield
|
63
|
+
ActiveRecord::Base.slave_mock = conn
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def adapter_connection
|
68
|
+
ActiveRecord::Base.connection
|
69
|
+
end
|
70
|
+
|
71
|
+
SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ]
|
72
|
+
Clock = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock
|
73
|
+
|
74
|
+
before do
|
75
|
+
ActiveRecord::Base.establish_connection(database_setup)
|
76
|
+
end
|
77
|
+
|
78
|
+
after do
|
79
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'consistency' do
|
83
|
+
def zero
|
84
|
+
Clock.zero
|
85
|
+
end
|
86
|
+
|
87
|
+
def master_position(pos)
|
88
|
+
Clock.new('', pos)
|
89
|
+
end
|
90
|
+
|
91
|
+
def slave_should_report_clock(pos)
|
92
|
+
pos = Array(pos)
|
93
|
+
values = pos.map { |p| { 'Relay_Master_Log_File' => '', 'Exec_Master_Log_Pos' => p } }
|
94
|
+
slave_connection.
|
95
|
+
should_receive('select_one').exactly(pos.length).with('SHOW SLAVE STATUS').
|
96
|
+
and_return(*values)
|
97
|
+
end
|
98
|
+
|
99
|
+
def master_should_report_clock(pos)
|
100
|
+
pos = Array(pos)
|
101
|
+
values = pos.map { |p| { 'File' => '', 'Position' => p } }
|
102
|
+
master_connection.
|
103
|
+
should_receive('select_one').exactly(pos.length).with('SHOW MASTER STATUS').
|
104
|
+
and_return(*values)
|
105
|
+
end
|
106
|
+
|
107
|
+
SelectMethods.each do |method|
|
108
|
+
it "should send the method '#{method}' to the slave if nil is given" do
|
109
|
+
slave_should_report_clock(0)
|
110
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
111
|
+
new_clock = ActiveRecord::Base.with_consistency(nil) do
|
112
|
+
adapter_connection.send(method, 'testing')
|
113
|
+
end
|
114
|
+
new_clock.should be_a(Clock)
|
115
|
+
new_clock.should equal(zero)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should send the method '#{method}' to the slave if clock.zero is given" do
|
119
|
+
slave_should_report_clock(0)
|
120
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
121
|
+
old_clock = zero
|
122
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
123
|
+
adapter_connection.send(method, 'testing')
|
124
|
+
end
|
125
|
+
new_clock.should be_a(Clock)
|
126
|
+
new_clock.should equal(old_clock)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should send the method '#{method}' to the master if slave hasn't cought up to required clock yet" do
|
130
|
+
slave_should_report_clock(0)
|
131
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
132
|
+
old_clock = master_position(1)
|
133
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
134
|
+
adapter_connection.send(method, 'testing' )
|
135
|
+
end
|
136
|
+
new_clock.should be_a(Clock)
|
137
|
+
new_clock.should equal(old_clock)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should send the method '#{method}' to the master connection if there are open transactions" do
|
141
|
+
master_connection.stub!(:open_transactions).and_return(1)
|
142
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
143
|
+
old_clock = zero
|
144
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
145
|
+
adapter_connection.send(method, 'testing')
|
146
|
+
end
|
147
|
+
new_clock.should be_a(Clock)
|
148
|
+
new_clock.should equal(zero)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should send the method '#{method}' to the master after a write operation" do
|
152
|
+
slave_should_report_clock(0)
|
153
|
+
master_should_report_clock(2)
|
154
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
155
|
+
master_connection.should_receive('update').with('testing').and_return(true)
|
156
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
157
|
+
old_clock = zero
|
158
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
159
|
+
adapter_connection.send(method, 'testing') # slave
|
160
|
+
adapter_connection.send('update', 'testing') # master
|
161
|
+
adapter_connection.send(method, 'testing') # master
|
162
|
+
end
|
163
|
+
new_clock.should be_a(Clock)
|
164
|
+
new_clock.should > old_clock
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should update the clock after a transaction" do
|
169
|
+
slave_should_report_clock(0)
|
170
|
+
master_should_report_clock([0, 1, 1])
|
171
|
+
|
172
|
+
slave_connection.
|
173
|
+
should_receive('select_all').exactly(1).times.with('testing').
|
174
|
+
and_return(true)
|
175
|
+
|
176
|
+
master_connection.
|
177
|
+
should_receive('update').exactly(3).times.with('testing').
|
178
|
+
and_return(true)
|
179
|
+
master_connection.
|
180
|
+
should_receive('select_all').exactly(5).times.with('testing').
|
181
|
+
and_return(true)
|
182
|
+
%w(begin_db_transaction
|
183
|
+
commit_db_transaction
|
184
|
+
increment_open_transactions
|
185
|
+
decrement_open_transactions
|
186
|
+
outside_transaction?).each do |txstmt|
|
187
|
+
master_connection.should_receive(txstmt).exactly(1).times
|
188
|
+
end
|
189
|
+
|
190
|
+
master_connection.
|
191
|
+
should_receive('open_transactions').exactly(13).times.
|
192
|
+
and_return(
|
193
|
+
# adapter: with_consistency, select_all, update, select_all
|
194
|
+
0, 0, 0, 0,
|
195
|
+
# connection: transaction
|
196
|
+
0,
|
197
|
+
# adapter: select_all, update, select_all, commit_db_transaction
|
198
|
+
1, 1, 1, 0,
|
199
|
+
# connection: transaction (ensure)
|
200
|
+
0,
|
201
|
+
# adapter: select_all, update, select_all
|
202
|
+
0, 0, 0
|
203
|
+
)
|
204
|
+
|
205
|
+
old_clock = zero
|
206
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
207
|
+
adapter_connection.send('select_all', 'testing') # slave s=0 m=0
|
208
|
+
adapter_connection.send('update', 'testing') # master s=0 m=1
|
209
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=1
|
210
|
+
|
211
|
+
ActiveRecord::Base.transaction do
|
212
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=1
|
213
|
+
adapter_connection.send('update', 'testing') # master s=0 m=1
|
214
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=1
|
215
|
+
end
|
216
|
+
|
217
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=2
|
218
|
+
adapter_connection.send('update', 'testing') # master s=0 m=3
|
219
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=3
|
220
|
+
end
|
221
|
+
|
222
|
+
new_clock.should > old_clock
|
223
|
+
end
|
224
|
+
|
225
|
+
context "with nested with_consistency" do
|
226
|
+
it "should return the same clock if not writing and no lag" do
|
227
|
+
slave_should_report_clock(0) # note: tests memoizing slave clock
|
228
|
+
slave_connection.
|
229
|
+
should_receive('select_one').exactly(3).times.with('testing').
|
230
|
+
and_return(true)
|
231
|
+
|
232
|
+
old_clock = zero
|
233
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
234
|
+
adapter_connection.send('select_one', 'testing')
|
235
|
+
ActiveRecord::Base.with_consistency(old_clock) do
|
236
|
+
adapter_connection.send('select_one', 'testing')
|
237
|
+
end
|
238
|
+
adapter_connection.send('select_one', 'testing')
|
239
|
+
end
|
240
|
+
new_clock.should equal(old_clock)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "requesting a newer clock should return a new clock" do
|
244
|
+
adapter_connection.
|
245
|
+
should_receive('slave_consistent?').exactly(2).times.
|
246
|
+
and_return(true, false)
|
247
|
+
slave_connection.
|
248
|
+
should_receive('select_all').exactly(2).times.with('testing').
|
249
|
+
and_return(true)
|
250
|
+
master_connection.
|
251
|
+
should_receive('select_all').exactly(1).times.with('testing').
|
252
|
+
and_return(true)
|
253
|
+
|
254
|
+
start_clock = zero
|
255
|
+
inner_clock = zero
|
256
|
+
outer_clock = ActiveRecord::Base.with_consistency(start_clock) do
|
257
|
+
adapter_connection.send('select_all', 'testing') # slave
|
258
|
+
inner_clock = ActiveRecord::Base.with_consistency(master_position(1)) do
|
259
|
+
adapter_connection.send('select_all', 'testing') # master
|
260
|
+
end
|
261
|
+
adapter_connection.send('select_all', 'testing') # slave
|
262
|
+
end
|
263
|
+
|
264
|
+
start_clock.should equal(outer_clock)
|
265
|
+
inner_clock.should > start_clock
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should do the right thing when nested inside with_master" do
|
270
|
+
slave_should_report_clock(0)
|
271
|
+
slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
|
272
|
+
master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
|
273
|
+
ActiveRecord::Base.with_master do
|
274
|
+
adapter_connection.send('select_all', 'testing') # master
|
275
|
+
ActiveRecord::Base.with_consistency(zero) do
|
276
|
+
adapter_connection.send('select_all', 'testing') # slave
|
277
|
+
end
|
278
|
+
adapter_connection.send('select_all', 'testing') # master
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should do the right thing when nested inside with_slave" do
|
283
|
+
slave_should_report_clock(0)
|
284
|
+
slave_connection.should_receive('select_all').exactly(3).times.with('testing').and_return(true)
|
285
|
+
ActiveRecord::Base.with_slave do
|
286
|
+
adapter_connection.send('select_all', 'testing') # slave
|
287
|
+
ActiveRecord::Base.with_consistency(zero) do
|
288
|
+
adapter_connection.send('select_all', 'testing') # slave
|
289
|
+
end
|
290
|
+
adapter_connection.send('select_all', 'testing') # slave
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
it "should do the right thing when wrapping with_master" do
|
295
|
+
slave_should_report_clock(0)
|
296
|
+
slave_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
|
297
|
+
master_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
|
298
|
+
ActiveRecord::Base.with_consistency(zero) do
|
299
|
+
adapter_connection.send('select_all', 'testing') # slave
|
300
|
+
ActiveRecord::Base.with_master do
|
301
|
+
adapter_connection.send('select_all', 'testing') # master
|
302
|
+
end
|
303
|
+
adapter_connection.send('select_all', 'testing') # slave
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
it "should do the right thing when wrapping with_slave" do
|
308
|
+
slave_should_report_clock(0)
|
309
|
+
slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
|
310
|
+
master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
|
311
|
+
ActiveRecord::Base.with_consistency(master_position(1)) do
|
312
|
+
adapter_connection.send('select_all', 'testing') # master
|
313
|
+
ActiveRecord::Base.with_slave do
|
314
|
+
adapter_connection.send('select_all', 'testing') # slave
|
315
|
+
end
|
316
|
+
adapter_connection.send('select_all', 'testing') # master
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end # /with_consistency
|
320
|
+
end
|
metadata
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: master_slave_adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0.beta1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Mauricio Linhares
|
9
9
|
- Torsten Curdt
|
10
10
|
- Kim Altintop
|
11
11
|
- Omid Aladini
|
12
|
+
- Tiago Loureiro
|
13
|
+
- Tobias Schmidt
|
12
14
|
- SoundCloud
|
13
15
|
autorequire:
|
14
16
|
bindir: bin
|
15
17
|
cert_chain: []
|
16
|
-
date: 2012-05-
|
18
|
+
date: 2012-05-24 00:00:00.000000000 Z
|
17
19
|
dependencies:
|
18
20
|
- !ruby/object:Gem::Dependency
|
19
21
|
name: rspec
|
@@ -63,8 +65,8 @@ dependencies:
|
|
63
65
|
- - ~>
|
64
66
|
- !ruby/object:Gem::Version
|
65
67
|
version: 2.3.9
|
66
|
-
description: (MySQL) Replication Aware Master/Slave Database Adapter for
|
67
|
-
email: kim@soundcloud.com tcurdt@soundcloud.com
|
68
|
+
description: (MySQL) Replication Aware Master/Slave Database Adapter for ActiveRecord
|
69
|
+
email: kim@soundcloud.com tcurdt@soundcloud.com ts@soundcloud
|
68
70
|
executables: []
|
69
71
|
extensions: []
|
70
72
|
extra_rdoc_files: []
|
@@ -77,12 +79,16 @@ files:
|
|
77
79
|
- Rakefile
|
78
80
|
- Readme.md
|
79
81
|
- TODO.txt
|
80
|
-
- VERSION
|
81
82
|
- lib/active_record/connection_adapters/master_slave_adapter.rb
|
83
|
+
- lib/active_record/connection_adapters/master_slave_adapter/circuit_breaker.rb
|
84
|
+
- lib/active_record/connection_adapters/master_slave_adapter/clock.rb
|
85
|
+
- lib/active_record/connection_adapters/master_slave_adapter/version.rb
|
82
86
|
- lib/active_record/connection_adapters/mysql_master_slave_adapter.rb
|
83
87
|
- lib/master_slave_adapter.rb
|
84
88
|
- master_slave_adapter.gemspec
|
89
|
+
- spec/circuit_breaker_spec.rb
|
85
90
|
- spec/master_slave_adapter_spec.rb
|
91
|
+
- spec/mysql_master_slave_adapter_spec.rb
|
86
92
|
homepage: http://github.com/soundcloud/master_slave_adapter
|
87
93
|
licenses: []
|
88
94
|
post_install_message:
|
@@ -94,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
100
|
requirements:
|
95
101
|
- - ! '>='
|
96
102
|
- !ruby/object:Gem::Version
|
97
|
-
version: 1.
|
103
|
+
version: 1.8.7
|
98
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
105
|
none: false
|
100
106
|
requirements:
|
@@ -106,6 +112,8 @@ rubyforge_project:
|
|
106
112
|
rubygems_version: 1.8.21
|
107
113
|
signing_key:
|
108
114
|
specification_version: 3
|
109
|
-
summary: Replication Aware Master/Slave Database Adapter for
|
115
|
+
summary: Replication Aware Master/Slave Database Adapter for ActiveRecord
|
110
116
|
test_files:
|
117
|
+
- spec/circuit_breaker_spec.rb
|
111
118
|
- spec/master_slave_adapter_spec.rb
|
119
|
+
- spec/mysql_master_slave_adapter_spec.rb
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.2.0
|