replica_pools 2.2.1 → 2.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.
- checksums.yaml +4 -4
- data/README.md +17 -18
- data/lib/replica_pools/connection_proxy.rb +15 -25
- data/lib/replica_pools/engine.rb +61 -17
- data/lib/replica_pools/pools.rb +15 -13
- data/lib/replica_pools/query_cache.rb +11 -8
- data/lib/replica_pools/version.rb +1 -1
- data/lib/replica_pools.rb +3 -1
- metadata +18 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5964174c34b75f5e1da7b607a50541dc515a6e5591189fc2f4d342ed0ca5454d
|
4
|
+
data.tar.gz: 3af78fc30241c55c3f5edf098c353b334cc22ed50cd38b4bceb520dc94b55d6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
[](https://travis-ci.org/kickstarter/replica_pools)
|
5
|
+
[](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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
-
|
206
|
+
- https://github.com/schoefmax/multi_db
|
208
207
|
|
209
208
|
### Masochism
|
210
209
|
|
211
210
|
The original leader/replica plugin:
|
212
211
|
|
213
|
-
|
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
|
-
|
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
|
31
|
-
@replica_pools
|
32
|
-
@leader_depth
|
33
|
-
@current_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.
|
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
|
-
|
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)
|
data/lib/replica_pools/engine.rb
CHANGED
@@ -10,26 +10,70 @@ module ReplicaPools
|
|
10
10
|
|
11
11
|
ReplicaPools.config.safe_methods =
|
12
12
|
if ActiveRecord::VERSION::MAJOR == 3
|
13
|
-
[
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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."
|
data/lib/replica_pools/pools.rb
CHANGED
@@ -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
|
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.
|
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.
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
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.
|
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:
|
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:
|
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:
|
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.
|
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.
|
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.
|
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:
|