replica_pools 2.2.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9a32096014e9f2cd137adb7c8fe226173f40d9dea893a319c0dab24cbdf8371
4
- data.tar.gz: da62952df8213322963f8b8ce0c5da9ed8efe381c2348801794ec0ef0b6d294a
3
+ metadata.gz: 5964174c34b75f5e1da7b607a50541dc515a6e5591189fc2f4d342ed0ca5454d
4
+ data.tar.gz: 3af78fc30241c55c3f5edf098c353b334cc22ed50cd38b4bceb520dc94b55d6a
5
5
  SHA512:
6
- metadata.gz: 7f2d2abc5f7badb102c8116213b306d35336f6d54c651a74d6fcaa0e37c53c5282c46fbc643984ffde43f5e54d868051c38590372b8af4329741da1247b68128
7
- data.tar.gz: 51c4514ec6e0fa79ea2e6b128fd4d6d3866916d65d89ec743f49621678a5997c0dd3bc0bfe78a25acd72c39d61c5f13523a9f84a9160259966f9408f9c56b109
6
+ metadata.gz: f166935354ee63f6544ca88b3e62b05ebb77af42c6b147f3af95fd36de4029f390396f027bbf22e84c4a946f44ec80f8e1394376e18899009d5bb6948bf3f204
7
+ data.tar.gz: 0f9508142a03028c8ac484bdb16c2f8be8e9253d50027b3ca847f1859a4e4e1211f83c20b823c54f13dd9e09a5650548dc4526ffe4e9eae68c639c44d6745b95
data/README.md CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  Easy Single Leader / Multiple Replica Setup for use in Ruby/Rails projects
4
4
 
5
- [![Build
6
- Status](https://travis-ci.org/kickstarter/replica_pools.png?branch=owningit)](https://travis-ci.org/kickstarter/replica_pools)
5
+ [![Spec](https://github.com/kickstarter/replica_pools/actions/workflows/spec.yml/badge.svg)](https://github.com/kickstarter/replica_pools/actions/workflows/spec.yml)
7
6
 
8
7
  ## Overview
9
8
 
@@ -11,22 +10,22 @@ ReplicaPools replaces ActiveRecord's connection with a proxy that routes databas
11
10
 
12
11
  ReplicaPools also provides helpers so you can customize your replica strategy. You can organize replicas into pools and cycle through them (e.g. in a before_filter). You can make the connection default to the leader, or the default replica pool, and then use block helpers to temporarily change the behavior (e.g. in an around_filter).
13
12
 
14
- * Uses a naming convention in database.yml to designate replica pools.
15
- * Defaults to a given replica pool, but may also be configured to default to leader.
16
- * Routes database interactions (queries) to the right connection
17
- * Whitelisted queries go to the current connection (might be a replica).
18
- * All queries inside a transaction run on leader.
19
- * All other queries are also sent to the leader connection.
20
- * Supports ActiveRecord's in-memory query caching.
21
- * Helper methods can be used to easily load balance replicas, route traffic to different replica pools, or run directly against leader. (examples below)
13
+ - Uses a naming convention in database.yml to designate replica pools.
14
+ - Defaults to a given replica pool, but may also be configured to default to leader.
15
+ - Routes database interactions (queries) to the right connection
16
+ - Whitelisted queries go to the current connection (might be a replica).
17
+ - All queries inside a transaction run on leader.
18
+ - All other queries are also sent to the leader connection.
19
+ - Supports ActiveRecord's in-memory query caching.
20
+ - Helper methods can be used to easily load balance replicas, route traffic to different replica pools, or run directly against leader. (examples below)
22
21
 
23
22
  ## Not Supported
24
23
 
25
- * Sharding.
26
- * Automatic load balancing strategies.
27
- * Replica weights. You can accomplish this in your own load balancing strategy.
28
- * Whitelisting models that always use leader.
29
- * Blacklisting poorly performing replicas. This could cause load spikes on your leader. Whatever provisions your database.yml should make this choice.
24
+ - Sharding.
25
+ - Automatic load balancing strategies.
26
+ - Replica weights. You can accomplish this in your own load balancing strategy.
27
+ - Whitelisting models that always use leader.
28
+ - Blacklisting poorly performing replicas. This could cause load spikes on your leader. Whatever provisions your database.yml should make this choice.
30
29
 
31
30
  ## Installation and Setup
32
31
 
@@ -166,7 +165,7 @@ To disable queries to the leader database -- for instance, in a production
166
165
  console -- set the disable_leader configuration to false. This will raise
167
166
  a ReplicaPools::LeaderDisabled error:
168
167
 
169
- ReplicaPools.config.disable_leader = false
168
+ ReplicaPools.config.disable_leader = false
170
169
 
171
170
  ## Running specs
172
171
 
@@ -204,10 +203,10 @@ Released under the MIT license
204
203
 
205
204
  The project is based on:
206
205
 
207
- * https://github.com/schoefmax/multi_db
206
+ - https://github.com/schoefmax/multi_db
208
207
 
209
208
  ### Masochism
210
209
 
211
210
  The original leader/replica plugin:
212
211
 
213
- * http://github.com/technoweenie/masochism
212
+ - http://github.com/technoweenie/masochism
@@ -11,26 +11,18 @@ module ReplicaPools
11
11
  class << self
12
12
  def generate_safe_delegations
13
13
  ReplicaPools.config.safe_methods.each do |method|
14
- generate_safe_delegation(method) unless instance_methods.include?(method)
14
+ self.define_method(method) do |*args, &block|
15
+ route_to(current, method, *args, &block)
16
+ end unless instance_methods.include?(method)
15
17
  end
16
18
  end
17
-
18
- protected
19
-
20
- def generate_safe_delegation(method)
21
- class_eval <<-END, __FILE__, __LINE__ + 1
22
- def #{method}(*args, &block)
23
- route_to(current, :#{method}, *args, &block)
24
- end
25
- END
26
- end
27
19
  end
28
20
 
29
21
  def initialize(leader, pools)
30
- @leader = leader
31
- @replica_pools = pools
32
- @leader_depth = 0
33
- @current_pool = default_pool
22
+ @leader = leader
23
+ @replica_pools = pools
24
+ @leader_depth = 0
25
+ @current_pool = default_pool
34
26
 
35
27
  if ReplicaPools.config.defaults_to_leader
36
28
  @current = leader
@@ -41,7 +33,7 @@ module ReplicaPools
41
33
  # this ivar is for ConnectionAdapter compatibility
42
34
  # some gems (e.g. newrelic_rpm) will actually use
43
35
  # instance_variable_get(:@config) to find it.
44
- @config = current.connection_config
36
+ @config = current.connection_db_config
45
37
  end
46
38
 
47
39
  def with_pool(pool_name = 'default')
@@ -91,7 +83,13 @@ module ReplicaPools
91
83
  # Safe methods have been generated during `setup!`.
92
84
  # Creates a method to speed up subsequent calls.
93
85
  def method_missing(method, *args, &block)
94
- generate_unsafe_delegation(method)
86
+ self.class.define_method(method) do |*args, &block|
87
+ route_to(leader, method, *args, &block).tap do
88
+ if %i[insert delete update].include?(method)
89
+ leader.retrieve_connection.clear_query_cache
90
+ end
91
+ end
92
+ end
95
93
  send(method, *args, &block)
96
94
  end
97
95
 
@@ -99,14 +97,6 @@ module ReplicaPools
99
97
  leader_depth > 0
100
98
  end
101
99
 
102
- def generate_unsafe_delegation(method)
103
- self.class_eval <<-END, __FILE__, __LINE__ + 1
104
- def #{method}(*args, &block)
105
- route_to(leader, :#{method}, *args, &block)
106
- end
107
- END
108
- end
109
-
110
100
  def route_to(conn, method, *args, &block)
111
101
  raise ReplicaPools::LeaderDisabled.new if ReplicaPools.config.disable_leader && conn == leader
112
102
  conn.retrieve_connection.send(method, *args, &block)
@@ -10,26 +10,70 @@ module ReplicaPools
10
10
 
11
11
  ReplicaPools.config.safe_methods =
12
12
  if ActiveRecord::VERSION::MAJOR == 3
13
- [
14
- :select_all, :select_one, :select_value, :select_values,
15
- :select_rows, :select, :verify!, :raw_connection, :active?, :reconnect!,
16
- :disconnect!, :reset_runtime, :log, :log_info
13
+ %i[
14
+ active?
15
+ disconnect!
16
+ log
17
+ log_info
18
+ raw_connection
19
+ reconnect!
20
+ reset_runtime
21
+ select
22
+ select_all
23
+ select_one
24
+ select_rows
25
+ select_value
26
+ select_values
27
+ verify!
17
28
  ]
18
29
  elsif ActiveRecord::VERSION::MAJOR == 4
19
- [
20
- :select_all, :select_one, :select_value, :select_values,
21
- :select_rows, :select, :verify!, :raw_connection, :active?, :reconnect!,
22
- :disconnect!, :reset_runtime, :log
30
+ %i[
31
+ active?
32
+ disconnect!
33
+ log
34
+ raw_connection
35
+ reconnect!
36
+ reset_runtime
37
+ select
38
+ select_all
39
+ select_one
40
+ select_rows
41
+ select_value
42
+ select_values
43
+ verify!
23
44
  ]
24
- elsif [5, 6].include?(ActiveRecord::VERSION::MAJOR)
25
- [
26
- :select_all, :select_one, :select_value, :select_values,
27
- :select_rows, :select, :select_prepared, :verify!, :raw_connection,
28
- :active?, :reconnect!, :disconnect!, :reset_runtime, :log,
29
- :lookup_cast_type_from_column, :sanitize_limit,
30
- :combine_bind_parameters, :quote_table_name, :quote, :quote_column_names, :quote_table_names, :table_alias_for,
31
- :case_sensitive_comparison, :case_insensitive_comparison,
32
- :schema_cache, :cacheable_query, :prepared_statements, :clear_cache!, :column_name_for_operation
45
+ elsif [5, 6, 7].include?(ActiveRecord::VERSION::MAJOR)
46
+ %i[
47
+ active?
48
+ cacheable_query
49
+ case_insensitive_comparison
50
+ case_sensitive_comparison
51
+ clear_cache!
52
+ column_name_for_operation
53
+ column_name_with_order_matcher
54
+ combine_bind_parameters
55
+ disconnect!
56
+ log
57
+ lookup_cast_type_from_column
58
+ prepared_statements
59
+ quote
60
+ quote_column_names
61
+ quote_table_name
62
+ quote_table_names
63
+ raw_connection
64
+ reconnect!
65
+ reset_runtime
66
+ sanitize_limit
67
+ schema_cache
68
+ select
69
+ select_all
70
+ select_one
71
+ select_prepared
72
+ select_rows
73
+ select_value
74
+ select_values
75
+ table_alias_for
76
+ verify!
33
77
  ]
34
78
  else
35
79
  warn "Unsupported ActiveRecord version #{ActiveRecord.version}. Please whitelist the safe methods."
@@ -6,12 +6,12 @@ module ReplicaPools
6
6
 
7
7
  def initialize
8
8
  pools = {}
9
- pool_configurations.group_by{|_, name, _| name }.each do |name, set|
9
+ pool_configurations.group_by{ |_, name, _| name }.each do |name, set|
10
10
  pools[name.to_sym] = ReplicaPools::Pool.new(
11
11
  name,
12
- set.map{ |conn_name, _, replica_name|
12
+ set.map do |conn_name, _, replica_name|
13
13
  connection_class(name, replica_name, conn_name)
14
- }
14
+ end
15
15
  )
16
16
  end
17
17
 
@@ -36,7 +36,9 @@ module ReplicaPools
36
36
  def config_hash
37
37
  if ActiveRecord::VERSION::MAJOR >= 6
38
38
  # in Rails >= 6, `configurations` is an instance of ActiveRecord::DatabaseConfigurations
39
- ActiveRecord::Base.configurations.to_h
39
+ ActiveRecord::Base.configurations.configs_for.map do |c|
40
+ [c.env_name, c.configuration_hash.transform_keys(&:to_s)]
41
+ end.to_h
40
42
  else
41
43
  # in Rails < 6, it's just a hash
42
44
  ActiveRecord::Base.configurations
@@ -47,16 +49,16 @@ module ReplicaPools
47
49
  def connection_class(pool_name, replica_name, connection_name)
48
50
  class_name = "#{pool_name.camelize}#{replica_name.camelize}"
49
51
 
50
- ReplicaPools.module_eval %Q{
51
- class #{class_name} < ActiveRecord::Base
52
- self.abstract_class = true
53
- establish_connection :#{connection_name}
54
- def self.connection_config
55
- configurations[#{connection_name.to_s.inspect}]
56
- end
52
+ ReplicaPools.const_set(class_name, Class.new(ActiveRecord::Base) do |c|
53
+ c.abstract_class = true
54
+ c.define_singleton_method(:connection_config) do
55
+ configurations.configs_for(connection_name.to_s)
57
56
  end
58
- }, __FILE__, __LINE__
59
- ReplicaPools.const_get(class_name)
57
+ end)
58
+
59
+ ReplicaPools.const_get(class_name).tap do |c|
60
+ c.establish_connection(connection_name.to_sym)
61
+ end
60
62
  end
61
63
  end
62
64
  end
@@ -10,22 +10,17 @@ module ReplicaPools
10
10
 
11
11
  # these methods can all use the leader connection
12
12
  (query_cache_methods - [:select_all]).each do |method_name|
13
- module_eval <<-END, __FILE__, __LINE__ + 1
14
- def #{method_name}(*a, &b)
15
- ActiveRecord::Base.connection.#{method_name}(*a, &b)
16
- end
17
- END
13
+ define_method(method_name) do |*args, &block|
14
+ ActiveRecord::Base.connection.send(method_name, *args, &block)
15
+ end
18
16
  end
19
17
 
20
18
  # select_all is trickier. it needs to use the leader
21
19
  # connection for cache logic, but ultimately pass its query
22
20
  # through to whatever connection is current.
23
21
  def select_all(*args)
24
- # there may be more args for Rails 5.0+, but we only care about arel, name, and binds for caching.
25
22
  relation, name, raw_binds = args
26
23
 
27
- # Rails 6.2 breaks this method as locked? is no longer available
28
- # https://github.com/kickstarter/replica_pools/issues/26
29
24
  if !query_cache_enabled || locked?(relation)
30
25
  return route_to(current, :select_all, *args)
31
26
  end
@@ -49,6 +44,14 @@ module ReplicaPools
49
44
  end
50
45
  end
51
46
 
47
+ # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
48
+ # queries should not be cached.
49
+ def locked?(arel)
50
+ # This method was copied from Rails 6.1, since it has been removed in 7.0
51
+ arel = arel.arel if arel.is_a?(ActiveRecord::Relation)
52
+ arel.respond_to?(:locked) && arel.locked
53
+ end
54
+
52
55
  # these can use the unsafe delegation built into ConnectionProxy
53
56
  # [:insert, :update, :delete]
54
57
  end
@@ -1,3 +1,3 @@
1
1
  module ReplicaPools
2
- VERSION = "2.2.1"
2
+ VERSION = "2.4.0"
3
3
  end
data/lib/replica_pools.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'logger'
2
+
1
3
  require 'active_record'
2
4
  require 'replica_pools/config'
3
5
  require 'replica_pools/pool'
@@ -64,7 +66,7 @@ module ReplicaPools
64
66
  end
65
67
 
66
68
  def logger
67
- ActiveRecord::Base.logger
69
+ ActiveRecord::Base.logger || Logger.new(STDOUT)
68
70
  end
69
71
  end
70
72
  end
metadata CHANGED
@@ -1,44 +1,50 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: replica_pools
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Drabik
8
8
  - Lance Ivy
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-04-16 00:00:00.000000000 Z
12
+ date: 2022-07-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - ">="
18
+ - - ">"
19
+ - !ruby/object:Gem::Version
20
+ version: '6.0'
21
+ - - "<"
19
22
  - !ruby/object:Gem::Version
20
- version: 3.2.12
23
+ version: '8.0'
21
24
  type: :runtime
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
24
27
  requirements:
25
- - - ">="
28
+ - - ">"
29
+ - !ruby/object:Gem::Version
30
+ version: '6.0'
31
+ - - "<"
26
32
  - !ruby/object:Gem::Version
27
- version: 3.2.12
33
+ version: '8.0'
28
34
  - !ruby/object:Gem::Dependency
29
35
  name: mysql2
30
36
  requirement: !ruby/object:Gem::Requirement
31
37
  requirements:
32
38
  - - "~>"
33
39
  - !ruby/object:Gem::Version
34
- version: 0.4.4
40
+ version: '0.5'
35
41
  type: :development
36
42
  prerelease: false
37
43
  version_requirements: !ruby/object:Gem::Requirement
38
44
  requirements:
39
45
  - - "~>"
40
46
  - !ruby/object:Gem::Version
41
- version: 0.4.4
47
+ version: '0.5'
42
48
  - !ruby/object:Gem::Dependency
43
49
  name: rack
44
50
  requirement: !ruby/object:Gem::Requirement
@@ -137,7 +143,7 @@ homepage: https://github.com/kickstarter/replica_pools
137
143
  licenses:
138
144
  - MIT
139
145
  metadata: {}
140
- post_install_message:
146
+ post_install_message:
141
147
  rdoc_options: []
142
148
  require_paths:
143
149
  - lib
@@ -152,8 +158,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
158
  - !ruby/object:Gem::Version
153
159
  version: '1.2'
154
160
  requirements: []
155
- rubygems_version: 3.0.3
156
- signing_key:
161
+ rubygems_version: 3.1.6
162
+ signing_key:
157
163
  specification_version: 4
158
164
  summary: Connection proxy for ActiveRecord for leader / replica setups.
159
165
  test_files: