makara 0.4.1 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +5 -5
  2. data/.github/dependabot.yml +11 -0
  3. data/.github/workflows/CI.yml +88 -0
  4. data/.github/workflows/gem-publish-public.yml +36 -0
  5. data/.rspec +1 -1
  6. data/.rubocop.yml +15 -0
  7. data/.rubocop_todo.yml +670 -0
  8. data/CHANGELOG.md +69 -48
  9. data/Gemfile +1 -16
  10. data/README.md +8 -9
  11. data/Rakefile +1 -1
  12. data/gemfiles/activerecord_5.2.gemfile +8 -0
  13. data/gemfiles/activerecord_6.0.gemfile +8 -0
  14. data/gemfiles/activerecord_6.1.gemfile +8 -0
  15. data/gemfiles/activerecord_head.gemfile +6 -0
  16. data/lib/active_record/connection_adapters/jdbcmysql_makara_adapter.rb +4 -18
  17. data/lib/active_record/connection_adapters/jdbcpostgresql_makara_adapter.rb +4 -18
  18. data/lib/active_record/connection_adapters/makara_abstract_adapter.rb +107 -30
  19. data/lib/active_record/connection_adapters/makara_jdbcmysql_adapter.rb +4 -18
  20. data/lib/active_record/connection_adapters/makara_jdbcpostgresql_adapter.rb +4 -18
  21. data/lib/active_record/connection_adapters/makara_mysql2_adapter.rb +4 -20
  22. data/lib/active_record/connection_adapters/makara_postgis_adapter.rb +4 -19
  23. data/lib/active_record/connection_adapters/makara_postgresql_adapter.rb +4 -20
  24. data/lib/active_record/connection_adapters/mysql2_makara_adapter.rb +4 -20
  25. data/lib/active_record/connection_adapters/postgresql_makara_adapter.rb +4 -20
  26. data/lib/makara/cache.rb +0 -2
  27. data/lib/makara/config_parser.rb +7 -15
  28. data/lib/makara/connection_wrapper.rb +43 -22
  29. data/lib/makara/context.rb +1 -0
  30. data/lib/makara/cookie.rb +1 -0
  31. data/lib/makara/error_handler.rb +0 -9
  32. data/lib/makara/errors/all_connections_blacklisted.rb +0 -2
  33. data/lib/makara/errors/blacklist_connection.rb +0 -2
  34. data/lib/makara/errors/blacklisted_while_in_transaction.rb +12 -0
  35. data/lib/makara/errors/invalid_shard.rb +14 -0
  36. data/lib/makara/errors/makara_error.rb +0 -1
  37. data/lib/makara/errors/no_connections_available.rb +0 -2
  38. data/lib/makara/logging/logger.rb +0 -4
  39. data/lib/makara/logging/subscriber.rb +0 -2
  40. data/lib/makara/middleware.rb +1 -2
  41. data/lib/makara/pool.rb +49 -31
  42. data/lib/makara/proxy.rb +56 -30
  43. data/lib/makara/railtie.rb +0 -2
  44. data/lib/makara/strategies/abstract.rb +1 -0
  45. data/lib/makara/strategies/priority_failover.rb +2 -0
  46. data/lib/makara/strategies/round_robin.rb +1 -3
  47. data/lib/makara/strategies/shard_aware.rb +45 -0
  48. data/lib/makara/version.rb +1 -3
  49. data/lib/makara.rb +7 -6
  50. data/makara.gemspec +26 -3
  51. data/spec/active_record/connection_adapters/makara_abstract_adapter_error_handling_spec.rb +1 -6
  52. data/spec/active_record/connection_adapters/makara_abstract_adapter_spec.rb +0 -9
  53. data/spec/active_record/connection_adapters/makara_mysql2_adapter_spec.rb +9 -22
  54. data/spec/active_record/connection_adapters/makara_postgis_adapter_spec.rb +2 -10
  55. data/spec/active_record/connection_adapters/makara_postgresql_adapter_spec.rb +62 -18
  56. data/spec/cache_spec.rb +0 -1
  57. data/spec/config_parser_spec.rb +54 -56
  58. data/spec/connection_wrapper_spec.rb +1 -2
  59. data/spec/cookie_spec.rb +4 -4
  60. data/spec/middleware_spec.rb +2 -2
  61. data/spec/pool_spec.rb +25 -14
  62. data/spec/proxy_spec.rb +0 -4
  63. data/spec/spec_helper.rb +6 -1
  64. data/spec/strategies/priority_failover_spec.rb +3 -4
  65. data/spec/strategies/round_robin_spec.rb +4 -8
  66. data/spec/strategies/shard_aware_spec.rb +218 -0
  67. data/spec/support/deep_dup.rb +1 -1
  68. data/spec/support/helpers.rb +5 -5
  69. data/spec/support/mock_objects.rb +5 -4
  70. data/spec/support/mysql2_database.yml +2 -2
  71. data/spec/support/mysql2_database_with_custom_errors.yml +2 -2
  72. data/spec/support/pool_extensions.rb +0 -3
  73. data/spec/support/postgis_schema.rb +1 -1
  74. data/spec/support/postgresql_database.yml +0 -2
  75. data/spec/support/proxy_extensions.rb +1 -3
  76. data/spec/support/schema.rb +1 -1
  77. data/spec/support/user.rb +1 -2
  78. metadata +165 -20
  79. data/.travis.yml +0 -105
  80. data/gemfiles/ar-head.gemfile +0 -24
  81. data/gemfiles/ar30.gemfile +0 -36
  82. data/gemfiles/ar31.gemfile +0 -36
  83. data/gemfiles/ar32.gemfile +0 -36
  84. data/gemfiles/ar40.gemfile +0 -24
  85. data/gemfiles/ar41.gemfile +0 -24
  86. data/gemfiles/ar42.gemfile +0 -24
  87. data/gemfiles/ar50.gemfile +0 -24
  88. data/gemfiles/ar51.gemfile +0 -24
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Makara::Strategies::RoundRobin do
4
- let(:proxy){ FakeProxy.new({:makara => pool_config.merge(makara_config).merge(:connections => [])}) }
4
+ let(:proxy){ FakeProxy.new({makara: pool_config.merge(makara_config).merge(connections: [])}) }
5
5
  let(:pool){ Makara::Pool.new('test', proxy) }
6
- let(:pool_config){ {:blacklist_duration => 5} }
6
+ let(:pool_config){ {blacklist_duration: 5} }
7
7
  let(:makara_config) { {} }
8
8
  let(:strategy) { pool.strategy }
9
9
 
@@ -14,7 +14,7 @@ describe Makara::Strategies::RoundRobin do
14
14
  end
15
15
 
16
16
  context 'bad config' do
17
- let(:makara_config) { { :test_strategy => 'SomethingElse::Here' } }
17
+ let(:makara_config) { { test_strategy: 'SomethingElse::Here' } }
18
18
  it 'should raise name error' do
19
19
  expect {
20
20
  pool
@@ -23,13 +23,12 @@ describe Makara::Strategies::RoundRobin do
23
23
  end
24
24
 
25
25
  context 'given in config' do
26
- let(:makara_config) { { :test_strategy => 'round_robin' } }
26
+ let(:makara_config) { { test_strategy: 'round_robin' } }
27
27
  it 'should use the strategy' do
28
28
  expect(pool.strategy).to be_instance_of(Makara::Strategies::RoundRobin)
29
29
  end
30
30
  end
31
31
 
32
-
33
32
  it 'should loop through with weights' do
34
33
  wrapper_a = pool.add(pool_config){ FakeConnection.new(something: 'a') }
35
34
  wrapper_b = pool.add(pool_config){ FakeConnection.new(something: 'b') }
@@ -61,7 +60,4 @@ describe Makara::Strategies::RoundRobin do
61
60
  expect(strategy.next.something).to eql('c')
62
61
  expect(strategy.next.something).to eql('b')
63
62
  end
64
-
65
-
66
-
67
63
  end
@@ -0,0 +1,218 @@
1
+ require 'spec_helper'
2
+
3
+ describe Makara::Strategies::ShardAware do
4
+ def with_shard(shard_id)
5
+ begin
6
+ Thread.current['makara_shard_id'] = shard_id
7
+ yield
8
+ ensure
9
+ Thread.current['makara_shard_id'] = nil
10
+ end
11
+ end
12
+
13
+ describe "failover strategy with shard awareness," do
14
+ let(:proxy){ FakeProxy.new({makara: pool_config.merge(makara_config).merge(connections: [])}) }
15
+ let(:pool){ Makara::Pool.new('master', proxy) }
16
+ let(:pool_config){ { blacklist_duration: 5} }
17
+ let(:makara_config) { {
18
+ master_strategy: 'failover',
19
+ master_shard_aware: true,
20
+ master_default_shard: 'shard2'
21
+ } }
22
+ let(:strategy) { pool.strategy }
23
+
24
+ it 'should use the strategy' do
25
+ expect(pool.strategy).to be_instance_of(Makara::Strategies::ShardAware)
26
+ end
27
+
28
+ it 'should take the top weight for a given shard' do
29
+ wrapper_a = pool.add(pool_config.merge(shard_id: 'shard1')){ FakeConnection.new(something: 'a') }
30
+ wrapper_b = pool.add(pool_config.merge(shard_id: 'shard1', weight: 2)){ FakeConnection.new(something: 'b') }
31
+ wrapper_c = pool.add(pool_config.merge(weight: 2, shard_id: 'shard2')){ FakeConnection.new(something: 'c') }
32
+
33
+ # default shard
34
+ expect(strategy.current.something).to eql('c')
35
+ expect(strategy.next.something).to eql('c')
36
+ expect(strategy.next.something).to eql('c')
37
+
38
+ # shard1
39
+ with_shard('shard1') do
40
+ expect(strategy.current.something).to eql('b')
41
+ expect(strategy.next.something).to eql('b')
42
+ expect(strategy.next.something).to eql('b')
43
+ end
44
+
45
+ # shard2
46
+ with_shard('shard2') do
47
+ expect(strategy.current.something).to eql('c')
48
+ expect(strategy.next.something).to eql('c')
49
+ expect(strategy.next.something).to eql('c')
50
+ end
51
+ end
52
+
53
+ it 'should take given order within shard if no weights' do
54
+ wrapper_a = pool.add(pool_config.merge(shard_id: 'shard1')){ FakeConnection.new(something: 'a') }
55
+ wrapper_b = pool.add(pool_config.merge(shard_id: 'shard1')){ FakeConnection.new(something: 'b') }
56
+ wrapper_c = pool.add(pool_config.merge(shard_id: 'shard2')){ FakeConnection.new(something: 'c') }
57
+
58
+ # default shard
59
+ expect(strategy.current.something).to eql('c')
60
+ expect(strategy.next.something).to eql('c')
61
+ expect(strategy.next.something).to eql('c')
62
+
63
+ # shard1
64
+ with_shard('shard1') do
65
+ expect(strategy.current.something).to eql('a')
66
+ expect(strategy.next.something).to eql('a')
67
+ end
68
+
69
+ # shard2
70
+ with_shard('shard2') do
71
+ expect(strategy.current.something).to eql('c')
72
+ expect(strategy.next.something).to eql('c')
73
+ end
74
+ end
75
+
76
+ it 'should handle failover to next one within shard' do
77
+ wrapper_a = pool.add(pool_config.merge(shard_id: 'shard1')){ FakeConnection.new(something: 'a') }
78
+ wrapper_b = pool.add(pool_config.merge(shard_id: 'shard1')){ FakeConnection.new(something: 'b') }
79
+ wrapper_c = pool.add(pool_config.merge(shard_id: 'shard2')){ FakeConnection.new(something: 'c') }
80
+
81
+ # default shard
82
+ expect(strategy.current.something).to eql('c')
83
+ expect(strategy.next.something).to eql('c')
84
+ expect(strategy.next.something).to eql('c')
85
+
86
+ # skips a for shard1
87
+ with_shard('shard1') do
88
+ pool.provide do |connection|
89
+ if connection == wrapper_a
90
+ raise Makara::Errors::BlacklistConnection.new(wrapper_a, StandardError.new('failure'))
91
+ end
92
+ end
93
+ expect(strategy.current.something).to eql('b')
94
+ expect(strategy.next.something).to eql('b')
95
+ end
96
+
97
+ # shard2
98
+ with_shard('shard2') do
99
+ expect(strategy.current.something).to eql('c')
100
+ expect(strategy.next.something).to eql('c')
101
+ end
102
+ end
103
+ it 'raises error for invalid shard' do
104
+ with_shard('shard3') do
105
+ expect{strategy.current.something }.to raise_error(Makara::Errors::InvalidShard)
106
+ end
107
+ end
108
+ end
109
+
110
+ describe "round_robin strategy with shard awareness," do
111
+ let(:proxy){ FakeProxy.new({makara: pool_config.merge(makara_config).merge(connections: [])}) }
112
+ let(:pool){ Makara::Pool.new('master', proxy) }
113
+ let(:pool_config){ { blacklist_duration: 5} }
114
+ let(:makara_config) { {
115
+ master_strategy: 'round_robin',
116
+ master_shard_aware: true,
117
+ master_default_shard: 'shard2'
118
+ } }
119
+ let(:strategy) { pool.strategy }
120
+
121
+ it 'should use the strategy' do
122
+ expect(pool.strategy).to be_instance_of(Makara::Strategies::ShardAware)
123
+ end
124
+
125
+ it 'should loop through with weights within shard' do
126
+ wrapper_a = pool.add(pool_config.merge(shard_id: 'shard1')){ FakeConnection.new(something: 'a') }
127
+ wrapper_b = pool.add(pool_config.merge(shard_id: 'shard1', weight: 2)){ FakeConnection.new(something: 'b') }
128
+ wrapper_c = pool.add(pool_config.merge(weight: 2, shard_id: 'shard2')){ FakeConnection.new(something: 'c') }
129
+
130
+ # default shard
131
+ expect(strategy.current.something).to eql('c')
132
+ expect(strategy.next.something).to eql('c')
133
+ expect(strategy.next.something).to eql('c')
134
+
135
+ # shard1
136
+ with_shard('shard1') do
137
+ expect(strategy.current.something).to eql('a')
138
+ expect(strategy.next.something).to eql('b')
139
+ expect(strategy.next.something).to eql('b')
140
+ expect(strategy.next.something).to eql('a')
141
+ end
142
+
143
+ # shard2
144
+ with_shard('shard2') do
145
+ expect(strategy.current.something).to eql('c')
146
+ expect(strategy.next.something).to eql('c')
147
+ expect(strategy.next.something).to eql('c')
148
+ end
149
+ end
150
+
151
+ it 'should handle failover to next one within shard' do
152
+ wrapper_a = pool.add(pool_config.merge(shard_id: 'shard1')){ FakeConnection.new(something: 'a') }
153
+ wrapper_b = pool.add(pool_config.merge(shard_id: 'shard1')){ FakeConnection.new(something: 'b') }
154
+ wrapper_c = pool.add(pool_config.merge(shard_id: 'shard2')){ FakeConnection.new(something: 'c') }
155
+
156
+ # default shard
157
+ expect(strategy.current.something).to eql('c')
158
+ expect(strategy.next.something).to eql('c')
159
+ expect(strategy.next.something).to eql('c')
160
+
161
+ # skips a for shard1
162
+ with_shard('shard1') do
163
+ pool.provide do |connection|
164
+ if connection == wrapper_a
165
+ raise Makara::Errors::BlacklistConnection.new(wrapper_a, StandardError.new('failure'))
166
+ end
167
+ end
168
+ expect(strategy.current.something).to eql('b')
169
+ expect(strategy.next.something).to eql('b')
170
+ expect(strategy.next.something).to eql('b')
171
+ end
172
+
173
+ # shard2
174
+ with_shard('shard2') do
175
+ expect(strategy.current.something).to eql('c')
176
+ expect(strategy.next.something).to eql('c')
177
+ expect(strategy.next.something).to eql('c')
178
+ end
179
+ end
180
+ it 'raises error for invalid shard' do
181
+ with_shard('shard3') do
182
+ expect{strategy.current.something }.to raise_error(Makara::Errors::InvalidShard)
183
+ end
184
+ end
185
+ end
186
+
187
+ describe "uses the configured failover strategy when shard_aware set to false," do
188
+ let(:proxy){ FakeProxy.new({makara: pool_config.merge(makara_config).merge(connections: [])}) }
189
+ let(:pool){ Makara::Pool.new('master', proxy) }
190
+ let(:pool_config){ { blacklist_duration: 5} }
191
+ let(:makara_config) { {
192
+ master_strategy: 'failover',
193
+ master_shard_aware: false,
194
+ master_default_shard: 'shard2'
195
+ } }
196
+ let(:strategy) { pool.strategy }
197
+
198
+ it 'should use the failover strategy' do
199
+ expect(pool.strategy).to be_instance_of(Makara::Strategies::PriorityFailover)
200
+ end
201
+ end
202
+
203
+ describe "uses the configured roundrobin strategy when shard_aware set to false," do
204
+ let(:proxy){ FakeProxy.new({makara: pool_config.merge(makara_config).merge(connections: [])}) }
205
+ let(:pool){ Makara::Pool.new('master', proxy) }
206
+ let(:pool_config){ { blacklist_duration: 5} }
207
+ let(:makara_config) { {
208
+ master_strategy: 'round_robin',
209
+ master_shard_aware: false,
210
+ master_default_shard: 'shard2'
211
+ } }
212
+ let(:strategy) { pool.strategy }
213
+
214
+ it 'should use the failover strategy' do
215
+ expect(pool.strategy).to be_instance_of(Makara::Strategies::RoundRobin)
216
+ end
217
+ end
218
+ end
@@ -9,4 +9,4 @@ unless Hash.respond_to?(:deep_dup)
9
9
  duplicate
10
10
  end
11
11
  end
12
- end
12
+ end
@@ -11,16 +11,16 @@ module SpecHelpers
11
11
 
12
12
  def config(masters = 1, slaves = 2)
13
13
  connections = []
14
- masters.times{ connections << {:role => 'master'} }
15
- slaves.times{ connections << {:role => 'slave'} }
14
+ masters.times{ connections << {role: 'master'} }
15
+ slaves.times{ connections << {role: 'slave'} }
16
16
  {
17
- :makara => {
17
+ makara: {
18
18
  # Defaults:
19
19
  # :master_ttl => 5,
20
20
  # :blacklist_duration => 30,
21
21
  # :sticky => true
22
- :id => 'mock_mysql',
23
- :connections => connections
22
+ id: 'mock_mysql',
23
+ connections: connections
24
24
  }
25
25
  }
26
26
  end
@@ -1,7 +1,6 @@
1
1
  require 'active_record/connection_adapters/makara_abstract_adapter'
2
2
 
3
3
  class FakeConnection < Struct.new(:config)
4
-
5
4
  def ping
6
5
  'ping!'
7
6
  end
@@ -18,6 +17,10 @@ class FakeConnection < Struct.new(:config)
18
17
  true
19
18
  end
20
19
 
20
+ def open_transactions
21
+ (config || {}).fetch(:open_transactions, 0)
22
+ end
23
+
21
24
  def disconnect!
22
25
  true
23
26
  end
@@ -28,7 +31,6 @@ class FakeConnection < Struct.new(:config)
28
31
  end
29
32
 
30
33
  class FakeDatabaseAdapter < Struct.new(:config)
31
-
32
34
  def execute(sql, name = nil)
33
35
  []
34
36
  end
@@ -44,11 +46,9 @@ class FakeDatabaseAdapter < Struct.new(:config)
44
46
  def active?
45
47
  true
46
48
  end
47
-
48
49
  end
49
50
 
50
51
  class FakeProxy < Makara::Proxy
51
-
52
52
  send_to_all :ping
53
53
  hijack_method :execute
54
54
 
@@ -58,6 +58,7 @@ class FakeProxy < Makara::Proxy
58
58
 
59
59
  def needs_master?(method_name, args)
60
60
  return false if args.first =~ /^select/
61
+
61
62
  true
62
63
  end
63
64
  end
@@ -1,9 +1,9 @@
1
1
  test:
2
2
  adapter: 'mysql2_makara'
3
3
  database: 'makara_test'
4
- username: 'root'
4
+ host: <%= ENV["MYSQL_HOST"] %>
5
+ username: root
5
6
  password: ''
6
-
7
7
  timeout: 5000
8
8
  connect_timeout: 1
9
9
  read_timeout: 1
@@ -1,9 +1,9 @@
1
1
  test:
2
2
  adapter: 'mysql2_makara'
3
3
  database: 'makara_test'
4
- username: 'root'
4
+ host: <%= ENV["MYSQL_HOST"] %>
5
+ username: root
5
6
  password: ''
6
-
7
7
  timeout: 5000
8
8
  connect_timeout: 1
9
9
  read_timeout: 1
@@ -1,5 +1,4 @@
1
1
  module PoolExtensions
2
-
3
2
  def connections
4
3
  @connections
5
4
  end
@@ -7,8 +6,6 @@ module PoolExtensions
7
6
  def connection_count
8
7
  @connections.length
9
8
  end
10
-
11
9
  end
12
10
 
13
-
14
11
  Makara::Pool.send(:include, PoolExtensions)
@@ -5,7 +5,7 @@ conn.execute "create extension if not exists postgis"
5
5
  if conn.table_exists? "towns"
6
6
  conn.execute("TRUNCATE TABLE towns")
7
7
  else
8
- conn.create_table "towns", :force => true do |t|
8
+ conn.create_table "towns", force: true do |t|
9
9
  t.st_point "location"
10
10
  end
11
11
  end
@@ -1,8 +1,6 @@
1
1
  test:
2
2
  adapter: 'postgresql_makara'
3
3
  database: 'makara_test'
4
- username: 'root'
5
- password: ''
6
4
 
7
5
  timeout: 5000
8
6
 
@@ -1,7 +1,6 @@
1
1
  module ProxyExtensions
2
-
3
2
  attr_reader :master_pool, :slave_pool, :id
4
-
3
+
5
4
  def master_for?(sql)
6
5
  pool_for(sql) == master_pool
7
6
  end
@@ -27,7 +26,6 @@ module ProxyExtensions
27
26
  def sticky=(s)
28
27
  @sticky = s
29
28
  end
30
-
31
29
  end
32
30
 
33
31
  Makara::Proxy.send(:include, ProxyExtensions)
@@ -4,6 +4,6 @@ if conn.table_exists? "users"
4
4
  conn.execute("TRUNCATE TABLE users")
5
5
  else
6
6
  conn.create_table "users" do |t|
7
- t.string "name"
7
+ t.string "name"
8
8
  end
9
9
  end
data/spec/support/user.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  module Test
2
2
  class User < ::ActiveRecord::Base
3
-
4
3
  end
5
- end
4
+ end