ar_mysql_flexmaster 0.3.1 → 0.4.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.
- data/.travis.yml +0 -1
- data/Gemfile +1 -2
- data/ar_mysql_flexmaster.gemspec +2 -2
- data/gemfiles/rails2.gemfile +1 -1
- data/gemfiles/rails2.gemfile.lock +28 -29
- data/gemfiles/rails3.2.gemfile +1 -1
- data/gemfiles/rails3.2.gemfile.lock +49 -50
- data/gemfiles/rails3.gemfile +1 -1
- data/gemfiles/rails3.gemfile.lock +40 -41
- data/lib/active_record/connection_adapters/mysql_flexmaster_adapter.rb +93 -25
- data/test/ar_flexmaster_test.rb +101 -31
- data/unplanned_failovers.md +47 -0
- metadata +8 -7
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/ar_mysql_flexmaster.gemspec
CHANGED
|
@@ -12,12 +12,12 @@ Gem::Specification.new do |gem|
|
|
|
12
12
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
13
13
|
gem.name = "ar_mysql_flexmaster"
|
|
14
14
|
gem.require_paths = ["lib"]
|
|
15
|
-
gem.version = "0.
|
|
15
|
+
gem.version = "0.4.0"
|
|
16
16
|
|
|
17
17
|
gem.add_runtime_dependency("mysql2")
|
|
18
18
|
gem.add_runtime_dependency("activerecord")
|
|
19
19
|
gem.add_runtime_dependency("activesupport")
|
|
20
20
|
gem.add_development_dependency("appraisal")
|
|
21
21
|
gem.add_development_dependency("yaggy")
|
|
22
|
-
gem.add_development_dependency("mysql_isolated_server")
|
|
22
|
+
gem.add_development_dependency("mysql_isolated_server", "~> 0.1.1")
|
|
23
23
|
end
|
data/gemfiles/rails2.gemfile
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: /Users/ben/src/ar_mysql_flexmaster
|
|
3
3
|
specs:
|
|
4
|
-
ar_mysql_flexmaster (0.3.
|
|
4
|
+
ar_mysql_flexmaster (0.3.1)
|
|
5
5
|
activerecord
|
|
6
6
|
activesupport
|
|
7
7
|
mysql2
|
|
@@ -9,38 +9,37 @@ PATH
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: https://rubygems.org/
|
|
11
11
|
specs:
|
|
12
|
-
actionmailer (2.3.
|
|
13
|
-
actionpack (= 2.3.
|
|
14
|
-
actionpack (2.3.
|
|
15
|
-
activesupport (= 2.3.
|
|
16
|
-
rack (~> 1.1.
|
|
17
|
-
activerecord (2.3.
|
|
18
|
-
activesupport (= 2.3.
|
|
19
|
-
activeresource (2.3.
|
|
20
|
-
activesupport (= 2.3.
|
|
21
|
-
activesupport (2.3.
|
|
22
|
-
appraisal (0.5.
|
|
12
|
+
actionmailer (2.3.18)
|
|
13
|
+
actionpack (= 2.3.18)
|
|
14
|
+
actionpack (2.3.18)
|
|
15
|
+
activesupport (= 2.3.18)
|
|
16
|
+
rack (~> 1.1.0)
|
|
17
|
+
activerecord (2.3.18)
|
|
18
|
+
activesupport (= 2.3.18)
|
|
19
|
+
activeresource (2.3.18)
|
|
20
|
+
activesupport (= 2.3.18)
|
|
21
|
+
activesupport (2.3.18)
|
|
22
|
+
appraisal (0.5.2)
|
|
23
23
|
bundler
|
|
24
24
|
rake
|
|
25
25
|
columnize (0.3.6)
|
|
26
|
-
debugger (1.
|
|
26
|
+
debugger (1.5.0)
|
|
27
27
|
columnize (>= 0.3.1)
|
|
28
|
-
debugger-linecache (~> 1.
|
|
29
|
-
debugger-ruby_core_source (~> 1.
|
|
30
|
-
debugger-linecache (1.
|
|
31
|
-
|
|
32
|
-
debugger-ruby_core_source (1.1.6)
|
|
28
|
+
debugger-linecache (~> 1.2.0)
|
|
29
|
+
debugger-ruby_core_source (~> 1.2.0)
|
|
30
|
+
debugger-linecache (1.2.0)
|
|
31
|
+
debugger-ruby_core_source (1.2.0)
|
|
33
32
|
mysql2 (0.2.18)
|
|
34
|
-
mysql_isolated_server (0.
|
|
35
|
-
rack (1.1.
|
|
36
|
-
rails (2.3.
|
|
37
|
-
actionmailer (= 2.3.
|
|
38
|
-
actionpack (= 2.3.
|
|
39
|
-
activerecord (= 2.3.
|
|
40
|
-
activeresource (= 2.3.
|
|
41
|
-
activesupport (= 2.3.
|
|
33
|
+
mysql_isolated_server (0.1.1)
|
|
34
|
+
rack (1.1.6)
|
|
35
|
+
rails (2.3.18)
|
|
36
|
+
actionmailer (= 2.3.18)
|
|
37
|
+
actionpack (= 2.3.18)
|
|
38
|
+
activerecord (= 2.3.18)
|
|
39
|
+
activeresource (= 2.3.18)
|
|
40
|
+
activesupport (= 2.3.18)
|
|
42
41
|
rake (>= 0.8.3)
|
|
43
|
-
rake (10.0.
|
|
42
|
+
rake (10.0.4)
|
|
44
43
|
yaggy (0.1.7)
|
|
45
44
|
|
|
46
45
|
PLATFORMS
|
|
@@ -49,8 +48,8 @@ PLATFORMS
|
|
|
49
48
|
DEPENDENCIES
|
|
50
49
|
appraisal
|
|
51
50
|
ar_mysql_flexmaster!
|
|
52
|
-
debugger
|
|
51
|
+
debugger (~> 1.5.0)
|
|
53
52
|
mysql2 (~> 0.2.0)
|
|
54
|
-
mysql_isolated_server
|
|
53
|
+
mysql_isolated_server (~> 0.1.1)
|
|
55
54
|
rails (~> 2.3.15)
|
|
56
55
|
yaggy
|
data/gemfiles/rails3.2.gemfile
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: /Users/ben/src/ar_mysql_flexmaster
|
|
3
3
|
specs:
|
|
4
|
-
ar_mysql_flexmaster (0.3.
|
|
4
|
+
ar_mysql_flexmaster (0.3.1)
|
|
5
5
|
activerecord
|
|
6
6
|
activesupport
|
|
7
7
|
mysql2
|
|
@@ -9,96 +9,95 @@ PATH
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: https://rubygems.org/
|
|
11
11
|
specs:
|
|
12
|
-
actionmailer (3.2.
|
|
13
|
-
actionpack (= 3.2.
|
|
14
|
-
mail (~> 2.
|
|
15
|
-
actionpack (3.2.
|
|
16
|
-
activemodel (= 3.2.
|
|
17
|
-
activesupport (= 3.2.
|
|
12
|
+
actionmailer (3.2.13)
|
|
13
|
+
actionpack (= 3.2.13)
|
|
14
|
+
mail (~> 2.5.3)
|
|
15
|
+
actionpack (3.2.13)
|
|
16
|
+
activemodel (= 3.2.13)
|
|
17
|
+
activesupport (= 3.2.13)
|
|
18
18
|
builder (~> 3.0.0)
|
|
19
19
|
erubis (~> 2.7.0)
|
|
20
20
|
journey (~> 1.0.4)
|
|
21
|
-
rack (~> 1.4.
|
|
21
|
+
rack (~> 1.4.5)
|
|
22
22
|
rack-cache (~> 1.2)
|
|
23
23
|
rack-test (~> 0.6.1)
|
|
24
24
|
sprockets (~> 2.2.1)
|
|
25
|
-
activemodel (3.2.
|
|
26
|
-
activesupport (= 3.2.
|
|
25
|
+
activemodel (3.2.13)
|
|
26
|
+
activesupport (= 3.2.13)
|
|
27
27
|
builder (~> 3.0.0)
|
|
28
|
-
activerecord (3.2.
|
|
29
|
-
activemodel (= 3.2.
|
|
30
|
-
activesupport (= 3.2.
|
|
28
|
+
activerecord (3.2.13)
|
|
29
|
+
activemodel (= 3.2.13)
|
|
30
|
+
activesupport (= 3.2.13)
|
|
31
31
|
arel (~> 3.0.2)
|
|
32
32
|
tzinfo (~> 0.3.29)
|
|
33
|
-
activeresource (3.2.
|
|
34
|
-
activemodel (= 3.2.
|
|
35
|
-
activesupport (= 3.2.
|
|
36
|
-
activesupport (3.2.
|
|
37
|
-
i18n (
|
|
33
|
+
activeresource (3.2.13)
|
|
34
|
+
activemodel (= 3.2.13)
|
|
35
|
+
activesupport (= 3.2.13)
|
|
36
|
+
activesupport (3.2.13)
|
|
37
|
+
i18n (= 0.6.1)
|
|
38
38
|
multi_json (~> 1.0)
|
|
39
|
-
appraisal (0.5.
|
|
39
|
+
appraisal (0.5.2)
|
|
40
40
|
bundler
|
|
41
41
|
rake
|
|
42
42
|
arel (3.0.2)
|
|
43
43
|
builder (3.0.4)
|
|
44
44
|
columnize (0.3.6)
|
|
45
|
-
debugger (1.
|
|
45
|
+
debugger (1.5.0)
|
|
46
46
|
columnize (>= 0.3.1)
|
|
47
|
-
debugger-linecache (~> 1.
|
|
48
|
-
debugger-ruby_core_source (~> 1.
|
|
49
|
-
debugger-linecache (1.
|
|
50
|
-
|
|
51
|
-
debugger-ruby_core_source (1.1.6)
|
|
47
|
+
debugger-linecache (~> 1.2.0)
|
|
48
|
+
debugger-ruby_core_source (~> 1.2.0)
|
|
49
|
+
debugger-linecache (1.2.0)
|
|
50
|
+
debugger-ruby_core_source (1.2.0)
|
|
52
51
|
erubis (2.7.0)
|
|
53
|
-
hike (1.2.
|
|
52
|
+
hike (1.2.2)
|
|
54
53
|
i18n (0.6.1)
|
|
55
54
|
journey (1.0.4)
|
|
56
|
-
json (1.7.
|
|
57
|
-
mail (2.
|
|
55
|
+
json (1.7.7)
|
|
56
|
+
mail (2.5.3)
|
|
58
57
|
i18n (>= 0.4.0)
|
|
59
58
|
mime-types (~> 1.16)
|
|
60
59
|
treetop (~> 1.4.8)
|
|
61
|
-
mime-types (1.
|
|
62
|
-
multi_json (1.
|
|
60
|
+
mime-types (1.23)
|
|
61
|
+
multi_json (1.7.2)
|
|
63
62
|
mysql2 (0.3.11)
|
|
64
|
-
mysql_isolated_server (0.
|
|
63
|
+
mysql_isolated_server (0.1.1)
|
|
65
64
|
polyglot (0.3.3)
|
|
66
|
-
rack (1.4.
|
|
65
|
+
rack (1.4.5)
|
|
67
66
|
rack-cache (1.2)
|
|
68
67
|
rack (>= 0.4)
|
|
69
|
-
rack-ssl (1.3.
|
|
68
|
+
rack-ssl (1.3.3)
|
|
70
69
|
rack
|
|
71
70
|
rack-test (0.6.2)
|
|
72
71
|
rack (>= 1.0)
|
|
73
|
-
rails (3.2.
|
|
74
|
-
actionmailer (= 3.2.
|
|
75
|
-
actionpack (= 3.2.
|
|
76
|
-
activerecord (= 3.2.
|
|
77
|
-
activeresource (= 3.2.
|
|
78
|
-
activesupport (= 3.2.
|
|
72
|
+
rails (3.2.13)
|
|
73
|
+
actionmailer (= 3.2.13)
|
|
74
|
+
actionpack (= 3.2.13)
|
|
75
|
+
activerecord (= 3.2.13)
|
|
76
|
+
activeresource (= 3.2.13)
|
|
77
|
+
activesupport (= 3.2.13)
|
|
79
78
|
bundler (~> 1.0)
|
|
80
|
-
railties (= 3.2.
|
|
81
|
-
railties (3.2.
|
|
82
|
-
actionpack (= 3.2.
|
|
83
|
-
activesupport (= 3.2.
|
|
79
|
+
railties (= 3.2.13)
|
|
80
|
+
railties (3.2.13)
|
|
81
|
+
actionpack (= 3.2.13)
|
|
82
|
+
activesupport (= 3.2.13)
|
|
84
83
|
rack-ssl (~> 1.3.2)
|
|
85
84
|
rake (>= 0.8.7)
|
|
86
85
|
rdoc (~> 3.4)
|
|
87
86
|
thor (>= 0.14.6, < 2.0)
|
|
88
|
-
rake (10.0.
|
|
89
|
-
rdoc (3.12)
|
|
87
|
+
rake (10.0.4)
|
|
88
|
+
rdoc (3.12.2)
|
|
90
89
|
json (~> 1.4)
|
|
91
90
|
sprockets (2.2.2)
|
|
92
91
|
hike (~> 1.2)
|
|
93
92
|
multi_json (~> 1.0)
|
|
94
93
|
rack (~> 1.0)
|
|
95
94
|
tilt (~> 1.1, != 1.3.0)
|
|
96
|
-
thor (0.
|
|
97
|
-
tilt (1.3.
|
|
95
|
+
thor (0.18.1)
|
|
96
|
+
tilt (1.3.7)
|
|
98
97
|
treetop (1.4.12)
|
|
99
98
|
polyglot
|
|
100
99
|
polyglot (>= 0.3.1)
|
|
101
|
-
tzinfo (0.3.
|
|
100
|
+
tzinfo (0.3.37)
|
|
102
101
|
yaggy (0.1.7)
|
|
103
102
|
|
|
104
103
|
PLATFORMS
|
|
@@ -107,8 +106,8 @@ PLATFORMS
|
|
|
107
106
|
DEPENDENCIES
|
|
108
107
|
appraisal
|
|
109
108
|
ar_mysql_flexmaster!
|
|
110
|
-
debugger
|
|
109
|
+
debugger (~> 1.5.0)
|
|
111
110
|
mysql2 (~> 0.3.0)
|
|
112
|
-
mysql_isolated_server
|
|
111
|
+
mysql_isolated_server (~> 0.1.1)
|
|
113
112
|
rails (~> 3.2.0)
|
|
114
113
|
yaggy
|
data/gemfiles/rails3.gemfile
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: /Users/ben/src/ar_mysql_flexmaster
|
|
3
3
|
specs:
|
|
4
|
-
ar_mysql_flexmaster (0.3.
|
|
4
|
+
ar_mysql_flexmaster (0.3.1)
|
|
5
5
|
activerecord
|
|
6
6
|
activesupport
|
|
7
7
|
mysql2
|
|
@@ -10,12 +10,12 @@ GEM
|
|
|
10
10
|
remote: https://rubygems.org/
|
|
11
11
|
specs:
|
|
12
12
|
abstract (1.0.0)
|
|
13
|
-
actionmailer (3.0.
|
|
14
|
-
actionpack (= 3.0.
|
|
13
|
+
actionmailer (3.0.20)
|
|
14
|
+
actionpack (= 3.0.20)
|
|
15
15
|
mail (~> 2.2.19)
|
|
16
|
-
actionpack (3.0.
|
|
17
|
-
activemodel (= 3.0.
|
|
18
|
-
activesupport (= 3.0.
|
|
16
|
+
actionpack (3.0.20)
|
|
17
|
+
activemodel (= 3.0.20)
|
|
18
|
+
activesupport (= 3.0.20)
|
|
19
19
|
builder (~> 2.1.2)
|
|
20
20
|
erubis (~> 2.6.6)
|
|
21
21
|
i18n (~> 0.5.0)
|
|
@@ -23,72 +23,71 @@ GEM
|
|
|
23
23
|
rack-mount (~> 0.6.14)
|
|
24
24
|
rack-test (~> 0.5.7)
|
|
25
25
|
tzinfo (~> 0.3.23)
|
|
26
|
-
activemodel (3.0.
|
|
27
|
-
activesupport (= 3.0.
|
|
26
|
+
activemodel (3.0.20)
|
|
27
|
+
activesupport (= 3.0.20)
|
|
28
28
|
builder (~> 2.1.2)
|
|
29
29
|
i18n (~> 0.5.0)
|
|
30
|
-
activerecord (3.0.
|
|
31
|
-
activemodel (= 3.0.
|
|
32
|
-
activesupport (= 3.0.
|
|
30
|
+
activerecord (3.0.20)
|
|
31
|
+
activemodel (= 3.0.20)
|
|
32
|
+
activesupport (= 3.0.20)
|
|
33
33
|
arel (~> 2.0.10)
|
|
34
34
|
tzinfo (~> 0.3.23)
|
|
35
|
-
activeresource (3.0.
|
|
36
|
-
activemodel (= 3.0.
|
|
37
|
-
activesupport (= 3.0.
|
|
38
|
-
activesupport (3.0.
|
|
39
|
-
appraisal (0.5.
|
|
35
|
+
activeresource (3.0.20)
|
|
36
|
+
activemodel (= 3.0.20)
|
|
37
|
+
activesupport (= 3.0.20)
|
|
38
|
+
activesupport (3.0.20)
|
|
39
|
+
appraisal (0.5.2)
|
|
40
40
|
bundler
|
|
41
41
|
rake
|
|
42
42
|
arel (2.0.10)
|
|
43
43
|
builder (2.1.2)
|
|
44
44
|
columnize (0.3.6)
|
|
45
|
-
debugger (1.
|
|
45
|
+
debugger (1.5.0)
|
|
46
46
|
columnize (>= 0.3.1)
|
|
47
|
-
debugger-linecache (~> 1.
|
|
48
|
-
debugger-ruby_core_source (~> 1.
|
|
49
|
-
debugger-linecache (1.
|
|
50
|
-
|
|
51
|
-
debugger-ruby_core_source (1.1.6)
|
|
47
|
+
debugger-linecache (~> 1.2.0)
|
|
48
|
+
debugger-ruby_core_source (~> 1.2.0)
|
|
49
|
+
debugger-linecache (1.2.0)
|
|
50
|
+
debugger-ruby_core_source (1.2.0)
|
|
52
51
|
erubis (2.6.6)
|
|
53
52
|
abstract (>= 1.0.0)
|
|
54
53
|
i18n (0.5.0)
|
|
55
|
-
json (1.7.
|
|
54
|
+
json (1.7.7)
|
|
56
55
|
mail (2.2.19)
|
|
57
56
|
activesupport (>= 2.3.6)
|
|
58
57
|
i18n (>= 0.4.0)
|
|
59
58
|
mime-types (~> 1.16)
|
|
60
59
|
treetop (~> 1.4.8)
|
|
61
|
-
mime-types (1.
|
|
60
|
+
mime-types (1.23)
|
|
62
61
|
mysql2 (0.2.18)
|
|
63
|
-
mysql_isolated_server (0.
|
|
62
|
+
mysql_isolated_server (0.1.1)
|
|
64
63
|
polyglot (0.3.3)
|
|
65
|
-
rack (1.2.
|
|
64
|
+
rack (1.2.8)
|
|
66
65
|
rack-mount (0.6.14)
|
|
67
66
|
rack (>= 1.0.0)
|
|
68
67
|
rack-test (0.5.7)
|
|
69
68
|
rack (>= 1.0)
|
|
70
|
-
rails (3.0.
|
|
71
|
-
actionmailer (= 3.0.
|
|
72
|
-
actionpack (= 3.0.
|
|
73
|
-
activerecord (= 3.0.
|
|
74
|
-
activeresource (= 3.0.
|
|
75
|
-
activesupport (= 3.0.
|
|
69
|
+
rails (3.0.20)
|
|
70
|
+
actionmailer (= 3.0.20)
|
|
71
|
+
actionpack (= 3.0.20)
|
|
72
|
+
activerecord (= 3.0.20)
|
|
73
|
+
activeresource (= 3.0.20)
|
|
74
|
+
activesupport (= 3.0.20)
|
|
76
75
|
bundler (~> 1.0)
|
|
77
|
-
railties (= 3.0.
|
|
78
|
-
railties (3.0.
|
|
79
|
-
actionpack (= 3.0.
|
|
80
|
-
activesupport (= 3.0.
|
|
76
|
+
railties (= 3.0.20)
|
|
77
|
+
railties (3.0.20)
|
|
78
|
+
actionpack (= 3.0.20)
|
|
79
|
+
activesupport (= 3.0.20)
|
|
81
80
|
rake (>= 0.8.7)
|
|
82
81
|
rdoc (~> 3.4)
|
|
83
82
|
thor (~> 0.14.4)
|
|
84
|
-
rake (10.0.
|
|
85
|
-
rdoc (3.12)
|
|
83
|
+
rake (10.0.4)
|
|
84
|
+
rdoc (3.12.2)
|
|
86
85
|
json (~> 1.4)
|
|
87
86
|
thor (0.14.6)
|
|
88
87
|
treetop (1.4.12)
|
|
89
88
|
polyglot
|
|
90
89
|
polyglot (>= 0.3.1)
|
|
91
|
-
tzinfo (0.3.
|
|
90
|
+
tzinfo (0.3.37)
|
|
92
91
|
yaggy (0.1.7)
|
|
93
92
|
|
|
94
93
|
PLATFORMS
|
|
@@ -97,8 +96,8 @@ PLATFORMS
|
|
|
97
96
|
DEPENDENCIES
|
|
98
97
|
appraisal
|
|
99
98
|
ar_mysql_flexmaster!
|
|
100
|
-
debugger
|
|
99
|
+
debugger (~> 1.5.0)
|
|
101
100
|
mysql2 (~> 0.2.0)
|
|
102
|
-
mysql_isolated_server
|
|
101
|
+
mysql_isolated_server (~> 0.1.1)
|
|
103
102
|
rails (~> 3.0.0)
|
|
104
103
|
yaggy
|
|
@@ -35,36 +35,37 @@ module ActiveRecord
|
|
|
35
35
|
def initialize(logger, config)
|
|
36
36
|
@select_counter = 0
|
|
37
37
|
@config = config
|
|
38
|
-
@
|
|
38
|
+
@rw = config[:slave] ? :read : :write
|
|
39
39
|
@tx_hold_timeout = @config[:tx_hold_timeout] || DEFAULT_TX_HOLD_TIMEOUT
|
|
40
40
|
@connection_timeout = @config[:connection_timeout] || DEFAULT_CONNECT_TIMEOUT
|
|
41
41
|
|
|
42
|
-
connection = find_correct_host
|
|
42
|
+
connection = find_correct_host(@rw)
|
|
43
43
|
|
|
44
44
|
raise_no_server_available! unless connection
|
|
45
45
|
super(connection, logger, [], config)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def begin_db_transaction
|
|
49
|
-
if !
|
|
50
|
-
|
|
49
|
+
if !in_transaction?
|
|
50
|
+
with_lost_cx_guard { hard_verify }
|
|
51
51
|
end
|
|
52
52
|
super
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def execute(sql, name = nil)
|
|
56
|
-
if
|
|
57
|
-
|
|
56
|
+
if in_transaction?
|
|
57
|
+
super # no way to rescue any lost cx or wrong-host errors at this point.
|
|
58
58
|
else
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
with_lost_cx_guard do
|
|
60
|
+
if has_side_effects?(sql)
|
|
61
|
+
hard_verify
|
|
62
|
+
else
|
|
63
|
+
soft_verify
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
super
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
|
-
super
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
def current_host
|
|
@@ -77,9 +78,77 @@ module ActiveRecord
|
|
|
77
78
|
|
|
78
79
|
private
|
|
79
80
|
|
|
81
|
+
def in_transaction?
|
|
82
|
+
open_transactions > 0
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# never try to carry on if inside a transaction
|
|
86
|
+
# otherwise try to detect when the master/slave has crashed and retry stuff.
|
|
87
|
+
def with_lost_cx_guard
|
|
88
|
+
retried = false
|
|
89
|
+
|
|
90
|
+
begin
|
|
91
|
+
yield
|
|
92
|
+
rescue Mysql2::Error, ActiveRecord::StatementInvalid => e
|
|
93
|
+
if retryable_error?(e) && !retried
|
|
94
|
+
retried = true
|
|
95
|
+
@connection = nil
|
|
96
|
+
retry
|
|
97
|
+
else
|
|
98
|
+
raise e
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
AR_MESSAGES = [ /^Mysql2::Error: MySQL server has gone away/,
|
|
104
|
+
/^Mysql2::Error: Can't connect to MySQL server/ ]
|
|
105
|
+
def retryable_error?(e)
|
|
106
|
+
case e
|
|
107
|
+
when Mysql2::Error
|
|
108
|
+
# 2006 is gone-away, 61 is can't-connect (for reconnection: true connections)
|
|
109
|
+
[2006, 61].include?(e.errno)
|
|
110
|
+
when ActiveRecord::StatementInvalid
|
|
111
|
+
AR_MESSAGES.any? { |m| e.message.match(m) }
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# when either doing BEGIN or INSERT/UPDATE/DELETE etc, ensure a correct connection
|
|
116
|
+
# and crash if wrong
|
|
117
|
+
def hard_verify
|
|
118
|
+
if !@connection || !cx_correct?
|
|
119
|
+
refind_correct_host!
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# on select statements, check every 10 statements to see if we need to switch hosts,
|
|
124
|
+
# but don't crash if the cx is wrong, and don't sleep trying to find a correct one.
|
|
125
|
+
def soft_verify
|
|
126
|
+
if !@connection
|
|
127
|
+
@connection = find_correct_host(@rw)
|
|
128
|
+
else
|
|
129
|
+
@select_counter += 1
|
|
130
|
+
return unless @select_counter % CHECK_EVERY_N_SELECTS == 0
|
|
131
|
+
|
|
132
|
+
if !cx_correct?
|
|
133
|
+
cx = find_correct_host(@rw)
|
|
134
|
+
@connection = cx if cx
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
if @rw == :write && !@connection
|
|
138
|
+
# desperation mode: we've been asked for the master, but it's just not available.
|
|
139
|
+
# we'll go ahead and return a connection to the slave, understanding that it'll never work
|
|
140
|
+
# for writes.
|
|
141
|
+
@connection = find_correct_host(:read)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def has_side_effects?(sql)
|
|
146
|
+
sql =~ /^\s*(INSERT|UPDATE|DELETE|ALTER|CHANGE|REPLACE)/i
|
|
147
|
+
end
|
|
148
|
+
|
|
80
149
|
def connect
|
|
81
|
-
@connection = find_correct_host
|
|
82
|
-
|
|
150
|
+
@connection = find_correct_host(@rw)
|
|
151
|
+
raise_no_server_available! unless @connection
|
|
83
152
|
end
|
|
84
153
|
|
|
85
154
|
def raise_no_server_available!
|
|
@@ -106,11 +175,9 @@ module ActiveRecord
|
|
|
106
175
|
tries = @tx_hold_timeout.to_f / sleep_interval
|
|
107
176
|
|
|
108
177
|
tries.to_i.times do
|
|
109
|
-
|
|
110
|
-
if
|
|
111
|
-
|
|
112
|
-
return
|
|
113
|
-
end
|
|
178
|
+
@connection = find_correct_host(@rw)
|
|
179
|
+
return if @connection
|
|
180
|
+
|
|
114
181
|
sleep(sleep_interval)
|
|
115
182
|
end
|
|
116
183
|
raise_no_server_available!
|
|
@@ -124,7 +191,7 @@ module ActiveRecord
|
|
|
124
191
|
end
|
|
125
192
|
end
|
|
126
193
|
|
|
127
|
-
def find_correct_host
|
|
194
|
+
def find_correct_host(rw)
|
|
128
195
|
cxs = hosts_and_ports.map do |host, port|
|
|
129
196
|
initialize_connection(host, port)
|
|
130
197
|
end.compact
|
|
@@ -132,7 +199,8 @@ module ActiveRecord
|
|
|
132
199
|
correct_cxs = cxs.select { |cx| cx_correct?(cx) }
|
|
133
200
|
|
|
134
201
|
chosen_cx = nil
|
|
135
|
-
|
|
202
|
+
case rw
|
|
203
|
+
when :write
|
|
136
204
|
# for master connections, we make damn sure that we have just one master
|
|
137
205
|
if correct_cxs.size == 1
|
|
138
206
|
chosen_cx = correct_cxs.first
|
|
@@ -146,8 +214,8 @@ module ActiveRecord
|
|
|
146
214
|
|
|
147
215
|
chosen_cx = nil
|
|
148
216
|
end
|
|
149
|
-
|
|
150
|
-
# for slave connections, we just return a random RO candidate or the master if none are available
|
|
217
|
+
when :read
|
|
218
|
+
# for slave connections (or master-gone-away scenarios), we just return a random RO candidate or the master if none are available
|
|
151
219
|
if correct_cxs.empty?
|
|
152
220
|
chosen_cx = cxs.first
|
|
153
221
|
else
|
|
@@ -178,7 +246,7 @@ module ActiveRecord
|
|
|
178
246
|
def cx_correct?(cx = @connection)
|
|
179
247
|
res = cx.query("SELECT @@read_only as ro").first
|
|
180
248
|
|
|
181
|
-
if @
|
|
249
|
+
if @rw == :write
|
|
182
250
|
res.first == 0
|
|
183
251
|
else
|
|
184
252
|
res.first == 1
|
data/test/ar_flexmaster_test.rb
CHANGED
|
@@ -7,20 +7,27 @@ require 'debugger'
|
|
|
7
7
|
|
|
8
8
|
File.open(File.dirname(File.expand_path(__FILE__)) + "/database.yml", "w+") do |f|
|
|
9
9
|
f.write <<-EOL
|
|
10
|
-
|
|
10
|
+
common: &common
|
|
11
11
|
adapter: mysql_flexmaster
|
|
12
12
|
username: flex
|
|
13
|
-
hosts: ["127.0.0.1:#{$mysql_master.port}", "127.0.0.1:#{$mysql_slave.port}"]
|
|
14
|
-
password:
|
|
13
|
+
hosts: ["127.0.0.1:#{$mysql_master.port}", "127.0.0.1:#{$mysql_slave.port}", "127.0.0.1:#{$mysql_slave_2.port}"]
|
|
15
14
|
database: flexmaster_test
|
|
16
15
|
|
|
16
|
+
test:
|
|
17
|
+
<<: *common
|
|
18
|
+
|
|
17
19
|
test_slave:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
<<: *common
|
|
21
|
+
slave: true
|
|
22
|
+
|
|
23
|
+
reconnect:
|
|
24
|
+
<<: *common
|
|
25
|
+
reconnect: true
|
|
26
|
+
|
|
27
|
+
reconnect_slave:
|
|
28
|
+
<<: *common
|
|
29
|
+
reconnect: true
|
|
20
30
|
slave: true
|
|
21
|
-
hosts: ["127.0.0.1:#{$mysql_master.port}", "127.0.0.1:#{$mysql_slave.port}", "127.0.0.1:#{$mysql_slave_2.port}"]
|
|
22
|
-
password:
|
|
23
|
-
database: flexmaster_test
|
|
24
31
|
EOL
|
|
25
32
|
end
|
|
26
33
|
|
|
@@ -32,7 +39,17 @@ end
|
|
|
32
39
|
|
|
33
40
|
class UserSlave < ActiveRecord::Base
|
|
34
41
|
establish_connection(:test_slave)
|
|
35
|
-
|
|
42
|
+
self.table_name = "users"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class Reconnect < ActiveRecord::Base
|
|
46
|
+
establish_connection(:reconnect)
|
|
47
|
+
self.table_name = "users"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class ReconnectSlave < ActiveRecord::Base
|
|
51
|
+
establish_connection(:reconnect_slave)
|
|
52
|
+
self.table_name = "users"
|
|
36
53
|
end
|
|
37
54
|
|
|
38
55
|
# $mysql_master and $mysql_slave are separate references to the master and slave that we
|
|
@@ -62,7 +79,7 @@ class TestArFlexmaster < Test::Unit::TestCase
|
|
|
62
79
|
end
|
|
63
80
|
|
|
64
81
|
def test_should_select_the_master_on_boot
|
|
65
|
-
|
|
82
|
+
assert_equal $mysql_master, master_connection
|
|
66
83
|
end
|
|
67
84
|
|
|
68
85
|
def test_should_hold_txs_until_timeout_then_abort
|
|
@@ -85,7 +102,7 @@ class TestArFlexmaster < Test::Unit::TestCase
|
|
|
85
102
|
$mysql_slave.set_rw(true)
|
|
86
103
|
end
|
|
87
104
|
User.create(:name => "foo")
|
|
88
|
-
|
|
105
|
+
assert_equal $mysql_slave, master_connection
|
|
89
106
|
assert User.first(:conditions => {:name => "foo"})
|
|
90
107
|
end
|
|
91
108
|
|
|
@@ -97,7 +114,9 @@ class TestArFlexmaster < Test::Unit::TestCase
|
|
|
97
114
|
$mysql_slave.set_rw(true)
|
|
98
115
|
end
|
|
99
116
|
User.update_all(:name => "bar")
|
|
100
|
-
|
|
117
|
+
|
|
118
|
+
assert_equal $mysql_slave, master_connection
|
|
119
|
+
|
|
101
120
|
assert_equal "bar", User.first.name
|
|
102
121
|
end
|
|
103
122
|
|
|
@@ -114,11 +133,11 @@ class TestArFlexmaster < Test::Unit::TestCase
|
|
|
114
133
|
ActiveRecord::Base.connection
|
|
115
134
|
$mysql_master.set_rw(false)
|
|
116
135
|
$mysql_slave.set_rw(true)
|
|
117
|
-
|
|
136
|
+
assert_equal $mysql_master, master_connection
|
|
118
137
|
100.times do
|
|
119
138
|
u = User.first
|
|
120
139
|
end
|
|
121
|
-
|
|
140
|
+
assert_equal $mysql_slave, master_connection
|
|
122
141
|
end
|
|
123
142
|
|
|
124
143
|
# there's a small window in which the old master is read-only but the new slave hasn't come online yet.
|
|
@@ -127,7 +146,7 @@ class TestArFlexmaster < Test::Unit::TestCase
|
|
|
127
146
|
ActiveRecord::Base.connection
|
|
128
147
|
$mysql_master.set_rw(false)
|
|
129
148
|
$mysql_slave.set_rw(false)
|
|
130
|
-
|
|
149
|
+
assert_equal $mysql_master, master_connection
|
|
131
150
|
100.times do
|
|
132
151
|
u = User.first
|
|
133
152
|
end
|
|
@@ -149,16 +168,17 @@ class TestArFlexmaster < Test::Unit::TestCase
|
|
|
149
168
|
assert_equal $mysql_master.port, cx.current_port
|
|
150
169
|
end
|
|
151
170
|
|
|
152
|
-
def
|
|
171
|
+
def test_should_move_off_the_slave_after_it_becomes_master
|
|
153
172
|
UserSlave.first
|
|
154
173
|
User.create!
|
|
155
174
|
$mysql_master.set_rw(false)
|
|
156
175
|
$mysql_slave.set_rw(true)
|
|
176
|
+
|
|
157
177
|
20.times do
|
|
158
178
|
UserSlave.connection.execute("select 1")
|
|
159
179
|
end
|
|
160
|
-
|
|
161
|
-
assert [$
|
|
180
|
+
|
|
181
|
+
assert [$mysql_master, $mysql_slave_2].include?(slave_connection)
|
|
162
182
|
end
|
|
163
183
|
|
|
164
184
|
def test_xxx_non_responsive_master
|
|
@@ -167,33 +187,70 @@ class TestArFlexmaster < Test::Unit::TestCase
|
|
|
167
187
|
start_time = Time.now.to_i
|
|
168
188
|
User.connection.reconnect!
|
|
169
189
|
assert Time.now.to_i - start_time >= 5, "only took #{Time.now.to_i - start_time} to timeout"
|
|
190
|
+
ensure
|
|
170
191
|
ActiveRecord::Base.configurations["test"]["hosts"].pop
|
|
171
192
|
end
|
|
172
193
|
|
|
173
|
-
def
|
|
194
|
+
def test_shooting_the_master_in_the_head
|
|
174
195
|
User.create!
|
|
196
|
+
UserSlave.first
|
|
197
|
+
|
|
198
|
+
$mysql_master.down!
|
|
175
199
|
|
|
176
|
-
|
|
177
|
-
|
|
200
|
+
# protected against 'gone away' errors?
|
|
201
|
+
assert User.first
|
|
178
202
|
|
|
179
|
-
|
|
203
|
+
# this statement should
|
|
204
|
+
# put us into a bad state -- our @connection should be nil, as we'll fail to get a master connection
|
|
205
|
+
assert_raises(ActiveRecord::ConnectionAdapters::MysqlFlexmasterAdapter::NoServerAvailableException) do
|
|
206
|
+
User.create!
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# now test that the next time through we ask for a read connection, we'll grudgingly give back the slave
|
|
210
|
+
User.first
|
|
211
|
+
|
|
212
|
+
assert [$mysql_slave, $mysql_slave_2].include?(master_connection)
|
|
213
|
+
|
|
214
|
+
# now a dba or someone comes along and flips the read-only bit on the slave
|
|
180
215
|
$mysql_slave.set_rw(true)
|
|
181
|
-
User.connection.reconnect!
|
|
182
216
|
User.create!
|
|
183
217
|
UserSlave.first
|
|
184
|
-
|
|
218
|
+
|
|
219
|
+
assert_equal $mysql_slave, master_connection
|
|
220
|
+
ensure
|
|
221
|
+
$mysql_master.up!
|
|
185
222
|
end
|
|
186
223
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
224
|
+
def test_losing_the_server_with_reconnect_on
|
|
225
|
+
Reconnect.create!
|
|
226
|
+
ReconnectSlave.first
|
|
227
|
+
|
|
228
|
+
$mysql_master.down!
|
|
229
|
+
|
|
230
|
+
assert Reconnect.first
|
|
231
|
+
assert ReconnectSlave.first
|
|
232
|
+
|
|
233
|
+
assert_raises(ActiveRecord::ConnectionAdapters::MysqlFlexmasterAdapter::NoServerAvailableException) do
|
|
234
|
+
Reconnect.create!
|
|
235
|
+
end
|
|
236
|
+
|
|
190
237
|
$mysql_slave.set_rw(true)
|
|
238
|
+
Reconnect.create!
|
|
239
|
+
ReconnectSlave.first
|
|
240
|
+
ensure
|
|
241
|
+
$mysql_master.up!
|
|
242
|
+
end
|
|
191
243
|
|
|
192
|
-
|
|
193
|
-
|
|
244
|
+
# test that when nothing else is available we can fall back to the master in a slave role
|
|
245
|
+
def test_master_can_act_as_slave
|
|
246
|
+
$mysql_slave.down!
|
|
247
|
+
$mysql_slave_2.down!
|
|
194
248
|
|
|
195
|
-
UserSlave.
|
|
196
|
-
|
|
249
|
+
UserSlave.first
|
|
250
|
+
assert_equal $mysql_master, slave_connection
|
|
251
|
+
ensure
|
|
252
|
+
$mysql_slave.up!
|
|
253
|
+
$mysql_slave_2.up!
|
|
197
254
|
end
|
|
198
255
|
|
|
199
256
|
|
|
@@ -207,4 +264,17 @@ class TestArFlexmaster < Test::Unit::TestCase
|
|
|
207
264
|
port = port_for_class(ActiveRecord::Base)
|
|
208
265
|
port == $original_master_port
|
|
209
266
|
end
|
|
267
|
+
|
|
268
|
+
def connection_for_class(klass)
|
|
269
|
+
port = port_for_class(klass)
|
|
270
|
+
[$mysql_master, $mysql_slave, $mysql_slave_2].find { |cx| cx.port == port }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def master_connection
|
|
274
|
+
connection_for_class(User)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def slave_connection
|
|
278
|
+
connection_for_class(UserSlave)
|
|
279
|
+
end
|
|
210
280
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Here's a logic flow I've been thinking about for dealing with master outages. In plain english, I'm generally
|
|
2
|
+
thinking:
|
|
3
|
+
|
|
4
|
+
- inside a transaction, we will never attempt to recover from a master failure or attempt to ensure proper connections
|
|
5
|
+
- outside a transaction, we'll try to be liberal about recovering from bad states and limp along in a read-only mode
|
|
6
|
+
as best we can.
|
|
7
|
+
|
|
8
|
+
guard:
|
|
9
|
+
begin
|
|
10
|
+
yield
|
|
11
|
+
rescue 'server gone away', "can't connect to server"
|
|
12
|
+
retry-once
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
hard_verify(INSERTs) == verify correct connection, try for 5 seconds, crash and unset connection if you can't
|
|
16
|
+
|
|
17
|
+
soft_verify(SELECTs) == every N requests, try to verify that your connection is the right one.
|
|
18
|
+
|
|
19
|
+
If you're running a SELECT targeted at the master, and no master is online, it's acceptable to use either
|
|
20
|
+
the old master (your existing connection) or a slave connection for the purposes of reads (until the next
|
|
21
|
+
verify)
|
|
22
|
+
|
|
23
|
+
This will allow us to limp along in read-only mode for a short time until a new master can be promoted.
|
|
24
|
+
The downside of this approach is that in a pathological case we could be making decisions based on stale
|
|
25
|
+
or incorrect data. I feel that the odds of this are long and are probably made up for by having a
|
|
26
|
+
halfway decent read-only mode.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
switch incoming_sql:
|
|
30
|
+
BEGIN:
|
|
31
|
+
- in transaction?
|
|
32
|
+
-> do not verify, do not guard.
|
|
33
|
+
|
|
34
|
+
- guard { hard-verify }
|
|
35
|
+
- execute BEGIN statement (without guard). hard to reconnect here because of side effects
|
|
36
|
+
|
|
37
|
+
INSERT/UPDATE/DELETE:
|
|
38
|
+
- in transaction?
|
|
39
|
+
-> do not verify, do not guard
|
|
40
|
+
-> guard { hard-verify, execute }
|
|
41
|
+
|
|
42
|
+
SELECT:
|
|
43
|
+
- in transaction?
|
|
44
|
+
-> no verify, no guard
|
|
45
|
+
-> guard { soft-verify / execute }
|
|
46
|
+
```
|
|
47
|
+
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ar_mysql_flexmaster
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
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: 2013-04-
|
|
12
|
+
date: 2013-04-25 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: mysql2
|
|
@@ -96,17 +96,17 @@ dependencies:
|
|
|
96
96
|
requirement: !ruby/object:Gem::Requirement
|
|
97
97
|
none: false
|
|
98
98
|
requirements:
|
|
99
|
-
- -
|
|
99
|
+
- - ~>
|
|
100
100
|
- !ruby/object:Gem::Version
|
|
101
|
-
version:
|
|
101
|
+
version: 0.1.1
|
|
102
102
|
type: :development
|
|
103
103
|
prerelease: false
|
|
104
104
|
version_requirements: !ruby/object:Gem::Requirement
|
|
105
105
|
none: false
|
|
106
106
|
requirements:
|
|
107
|
-
- -
|
|
107
|
+
- - ~>
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
|
-
version:
|
|
109
|
+
version: 0.1.1
|
|
110
110
|
description: ar_mysql_flexmaster allows configuring N mysql servers in database.yml
|
|
111
111
|
and auto-selects which is a master at runtime
|
|
112
112
|
email:
|
|
@@ -141,6 +141,7 @@ files:
|
|
|
141
141
|
- test/integration/there_and_back_again_test.rb
|
|
142
142
|
- test/integration/with_queries_to_be_killed_test.rb
|
|
143
143
|
- test/integration/wrong_setup_test.rb
|
|
144
|
+
- unplanned_failovers.md
|
|
144
145
|
homepage: http://github.com/osheroff/ar_mysql_flexmaster
|
|
145
146
|
licenses: []
|
|
146
147
|
post_install_message:
|
|
@@ -161,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
161
162
|
version: '0'
|
|
162
163
|
requirements: []
|
|
163
164
|
rubyforge_project:
|
|
164
|
-
rubygems_version: 1.8.
|
|
165
|
+
rubygems_version: 1.8.25
|
|
165
166
|
signing_key:
|
|
166
167
|
specification_version: 3
|
|
167
168
|
summary: select a master at runtime from a list
|