replica_pools 2.2.0.beta1 → 2.3.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 +22 -19
- data/lib/replica_pools/connection_proxy.rb +14 -24
- data/lib/replica_pools/engine.rb +60 -16
- data/lib/replica_pools/pools.rb +12 -12
- data/lib/replica_pools/query_cache.rb +6 -6
- data/lib/replica_pools/version.rb +1 -1
- data/lib/replica_pools.rb +3 -1
- data/spec/query_cache_spec.rb +13 -0
- metadata +26 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d9219fb04b0b4118f99202ad78486d565bb88fe57079f743336b381b50d5864
|
4
|
+
data.tar.gz: e3fefd1456ce775cf127e0561d5827b39d4a45fab0ef4359178bf58d841887bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1777856dd70f4fd661ef3a7ca6d149f57772e72e9636cd0ef674476c858fa8ce1e086a846a79e624924c1838765e39a88273fb0d3d6b01812c01be7668fe69c
|
7
|
+
data.tar.gz: 0f6f739901cfcf978c9d6b0b1a4e9efbd46c66c33954ab577186343c74eb5a9e499c15ed2d8fa6d8c97afc1f85854e81ef72cbeced472cc5cd0d7f0db59e3e15
|
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
|
|
@@ -186,11 +185,15 @@ Then you can run tests with:
|
|
186
185
|
$ bundle exec rake spec
|
187
186
|
```
|
188
187
|
|
188
|
+
## Releasing a New Version
|
189
|
+
|
190
|
+
First bump the version as appropriate in `lib/replica_pools/version.rb` and then run `bundle exec rake release`. This will push git tags to github and package and push the gem to rubygems.org.
|
191
|
+
|
189
192
|
## Authors
|
190
193
|
|
191
194
|
Author: Dan Drabik, Lance Ivy
|
192
195
|
|
193
|
-
Copyright (c) 2012-
|
196
|
+
Copyright (c) 2012-2021, Kickstarter
|
194
197
|
|
195
198
|
Released under the MIT license
|
196
199
|
|
@@ -200,10 +203,10 @@ Released under the MIT license
|
|
200
203
|
|
201
204
|
The project is based on:
|
202
205
|
|
203
|
-
|
206
|
+
- https://github.com/schoefmax/multi_db
|
204
207
|
|
205
208
|
### Masochism
|
206
209
|
|
207
210
|
The original leader/replica plugin:
|
208
211
|
|
209
|
-
|
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
|
@@ -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
45
|
elsif [5, 6].include?(ActiveRecord::VERSION::MAJOR)
|
25
|
-
[
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
|
@@ -47,16 +47,16 @@ module ReplicaPools
|
|
47
47
|
def connection_class(pool_name, replica_name, connection_name)
|
48
48
|
class_name = "#{pool_name.camelize}#{replica_name.camelize}"
|
49
49
|
|
50
|
-
ReplicaPools.
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
def self.connection_config
|
55
|
-
configurations[#{connection_name.to_s.inspect}]
|
56
|
-
end
|
50
|
+
ReplicaPools.const_set(class_name, Class.new(ActiveRecord::Base) do |c|
|
51
|
+
c.abstract_class = true
|
52
|
+
c.define_singleton_method(:connection_config) do
|
53
|
+
configurations[connection_name.to_s]
|
57
54
|
end
|
58
|
-
|
59
|
-
|
55
|
+
end)
|
56
|
+
|
57
|
+
ReplicaPools.const_get(class_name).tap do |c|
|
58
|
+
c.establish_connection(connection_name.to_sym)
|
59
|
+
end
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
@@ -10,11 +10,9 @@ 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
|
@@ -24,6 +22,8 @@ module ReplicaPools
|
|
24
22
|
# there may be more args for Rails 5.0+, but we only care about arel, name, and binds for caching.
|
25
23
|
relation, name, raw_binds = args
|
26
24
|
|
25
|
+
# Rails 6.2 breaks this method as locked? is no longer available
|
26
|
+
# https://github.com/kickstarter/replica_pools/issues/26
|
27
27
|
if !query_cache_enabled || locked?(relation)
|
28
28
|
return route_to(current, :select_all, *args)
|
29
29
|
end
|
@@ -32,7 +32,7 @@ module ReplicaPools
|
|
32
32
|
if raw_binds.blank? && relation.is_a?(ActiveRecord::Relation)
|
33
33
|
arel, binds = relation.arel, relation.bind_values
|
34
34
|
else
|
35
|
-
arel, binds = relation, raw_binds
|
35
|
+
arel, binds = relation, Array(raw_binds)
|
36
36
|
end
|
37
37
|
|
38
38
|
sql = to_sql(arel, binds)
|
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
|
data/spec/query_cache_spec.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rack'
|
2
2
|
require_relative 'spec_helper'
|
3
|
+
require_relative 'config/test_model'
|
3
4
|
|
4
5
|
describe ReplicaPools::QueryCache do
|
5
6
|
before(:each) do
|
@@ -103,4 +104,16 @@ describe ReplicaPools::QueryCache do
|
|
103
104
|
end
|
104
105
|
end
|
105
106
|
end
|
107
|
+
|
108
|
+
describe '.pluck regression test' do
|
109
|
+
it 'should work with query caching' do
|
110
|
+
TestModel.connection.enable_query_cache!
|
111
|
+
expect(TestModel.pluck(:id).count).to eql TestModel.all.count
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should work if query cache is not enabled' do
|
115
|
+
TestModel.connection.disable_query_cache!
|
116
|
+
expect(TestModel.pluck(:id).count).to eql TestModel.all.count
|
117
|
+
end
|
118
|
+
end
|
106
119
|
end
|
metadata
CHANGED
@@ -1,44 +1,44 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: replica_pools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.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-04-28 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
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
20
|
+
version: '6.0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- - "
|
25
|
+
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
27
|
+
version: '6.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: mysql2
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 0.
|
34
|
+
version: '0.5'
|
35
35
|
type: :development
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: 0.
|
41
|
+
version: '0.5'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: rack
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,6 +95,20 @@ dependencies:
|
|
95
95
|
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: pry
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
98
112
|
description: Connection proxy for ActiveRecord for leader / replica setups.
|
99
113
|
email: dan@kickstarter.com
|
100
114
|
executables: []
|
@@ -123,7 +137,7 @@ homepage: https://github.com/kickstarter/replica_pools
|
|
123
137
|
licenses:
|
124
138
|
- MIT
|
125
139
|
metadata: {}
|
126
|
-
post_install_message:
|
140
|
+
post_install_message:
|
127
141
|
rdoc_options: []
|
128
142
|
require_paths:
|
129
143
|
- lib
|
@@ -138,8 +152,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
152
|
- !ruby/object:Gem::Version
|
139
153
|
version: '1.2'
|
140
154
|
requirements: []
|
141
|
-
rubygems_version: 3.
|
142
|
-
signing_key:
|
155
|
+
rubygems_version: 3.1.6
|
156
|
+
signing_key:
|
143
157
|
specification_version: 4
|
144
158
|
summary: Connection proxy for ActiveRecord for leader / replica setups.
|
145
159
|
test_files:
|