active_record_shards 3.15.2 → 3.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -15
- data/lib/active_record_shards.rb +109 -6
- data/lib/active_record_shards/association_collection_connection_selection.rb +23 -14
- data/lib/active_record_shards/configuration_parser.rb +14 -5
- data/lib/active_record_shards/connection_handler.rb +1 -0
- data/lib/active_record_shards/connection_pool.rb +1 -0
- data/lib/active_record_shards/connection_specification.rb +1 -0
- data/lib/active_record_shards/{connection_switcher-4-0.rb → connection_switcher-4-2.rb} +2 -2
- data/lib/active_record_shards/connection_switcher.rb +63 -42
- data/lib/active_record_shards/default_replica_patches.rb +278 -0
- data/lib/active_record_shards/default_slave_patches.rb +3 -132
- data/lib/active_record_shards/deprecation.rb +12 -0
- data/lib/active_record_shards/migration.rb +5 -2
- data/lib/active_record_shards/model.rb +16 -11
- data/lib/active_record_shards/patches-4-2.rb +1 -0
- data/lib/active_record_shards/schema_dumper_extension.rb +6 -5
- data/lib/active_record_shards/shard_selection.rb +17 -14
- data/lib/active_record_shards/shard_support.rb +1 -0
- data/lib/active_record_shards/sql_comments.rb +7 -4
- data/lib/active_record_shards/tasks.rb +19 -13
- metadata +58 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24b77105aa8df10b1e79313f01b54fe87dba293938c9d41dc103f41cac64ec84
|
4
|
+
data.tar.gz: ec1da959a6dc2adbb6f287f73a3346c68fd49db30f7922dbc9adca7368532af2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ccae049b9c4a450f04cc2ae6b0cfca9eba75ac72d5f795c0c49ae5fc19e37fad1341d14f3ba34f75c684696131586a663fb306bcd35c2dad9fc625ad36955f3
|
7
|
+
data.tar.gz: 0b46d1cafa4fecae447a01106bfc4de592b7271fc17fef4c254a44a80cc22b22f5155e460d6aba036baad1a229f6f0b6935c3d108c935c5fd5cec9e6271eab83
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
[![
|
1
|
+
[![Build Status](https://github.com/zendesk/active_record_shards/workflows/CI/badge.svg)](https://github.com/zendesk/active_record_shards/actions?query=workflow%3ACI)
|
2
2
|
|
3
3
|
# ActiveRecord Shards
|
4
4
|
|
5
|
-
ActiveRecord Shards is an extension for ActiveRecord that provides support for sharded database and
|
5
|
+
ActiveRecord Shards is an extension for ActiveRecord that provides support for sharded database and replicas. Basically it is just a nice way to
|
6
6
|
switch between database connections. We've made the implementation very small, and have tried not to reinvent any wheels already present in ActiveRecord.
|
7
7
|
|
8
|
-
ActiveRecord Shards has been used and tested on Rails 4.2 and
|
8
|
+
ActiveRecord Shards has been used and tested on Rails 4.2, 5.x and 6.0, and has in some form or another been used in production on large Rails apps for several years.
|
9
9
|
|
10
10
|
- [Installation](#installation)
|
11
11
|
- [Configuration](#configuration)
|
@@ -24,7 +24,7 @@ and make sure to require 'active\_record\_shards' in some way.
|
|
24
24
|
|
25
25
|
## Configuration
|
26
26
|
|
27
|
-
Add the
|
27
|
+
Add the replica and shard configuration to config/database.yml:
|
28
28
|
|
29
29
|
```yaml
|
30
30
|
production:
|
@@ -35,19 +35,19 @@ production:
|
|
35
35
|
host: db1
|
36
36
|
username: root
|
37
37
|
password:
|
38
|
-
|
39
|
-
host:
|
38
|
+
replica:
|
39
|
+
host: db1_replica
|
40
40
|
shards:
|
41
41
|
1:
|
42
42
|
host: db_shard1
|
43
43
|
database: my_app_shard
|
44
|
-
|
45
|
-
host:
|
44
|
+
replica:
|
45
|
+
host: db_shard1_replica
|
46
46
|
2:
|
47
47
|
host: db_shard2
|
48
48
|
database: my_app_shard
|
49
|
-
|
50
|
-
host:
|
49
|
+
replica:
|
50
|
+
host: db_shard2_replica
|
51
51
|
```
|
52
52
|
|
53
53
|
basically connections inherit configuration from the parent configuration file.
|
@@ -140,23 +140,23 @@ class AccountMiddleware
|
|
140
140
|
end
|
141
141
|
```
|
142
142
|
|
143
|
-
You can switch to the
|
143
|
+
You can switch to the replica databases at any point by wrapping your code in an on\_replica block:
|
144
144
|
|
145
145
|
```ruby
|
146
|
-
ActiveRecord::Base.
|
146
|
+
ActiveRecord::Base.on_replica do
|
147
147
|
Account.find_by_big_expensive_query
|
148
148
|
end
|
149
149
|
```
|
150
150
|
|
151
|
-
This will perform the query on the
|
151
|
+
This will perform the query on the replica, and mark the returned instances as read only. There is also a shortcut for this:
|
152
152
|
|
153
153
|
```ruby
|
154
|
-
Account.
|
154
|
+
Account.on_replica.find_by_big_expensive_query
|
155
155
|
```
|
156
156
|
|
157
157
|
## Debugging
|
158
158
|
|
159
|
-
Show if a query went to
|
159
|
+
Show if a query went to primary or replica in the logs:
|
160
160
|
|
161
161
|
```Ruby
|
162
162
|
require 'active_record_shards/sql_comments'
|
data/lib/active_record_shards.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'active_record'
|
3
4
|
require 'active_record/base'
|
4
5
|
require 'active_record_shards/configuration_parser'
|
@@ -7,7 +8,7 @@ require 'active_record_shards/shard_selection'
|
|
7
8
|
require 'active_record_shards/connection_switcher'
|
8
9
|
require 'active_record_shards/association_collection_connection_selection'
|
9
10
|
require 'active_record_shards/migration'
|
10
|
-
require 'active_record_shards/
|
11
|
+
require 'active_record_shards/default_replica_patches'
|
11
12
|
require 'active_record_shards/schema_dumper_extension'
|
12
13
|
|
13
14
|
module ActiveRecordShards
|
@@ -22,17 +23,119 @@ end
|
|
22
23
|
ActiveRecord::Base.extend(ActiveRecordShards::ConfigurationParser)
|
23
24
|
ActiveRecord::Base.extend(ActiveRecordShards::Model)
|
24
25
|
ActiveRecord::Base.extend(ActiveRecordShards::ConnectionSwitcher)
|
25
|
-
ActiveRecord::Base.extend(ActiveRecordShards::
|
26
|
-
ActiveRecord::Relation.include(ActiveRecordShards::
|
26
|
+
ActiveRecord::Base.extend(ActiveRecordShards::DefaultReplicaPatches)
|
27
|
+
ActiveRecord::Relation.include(ActiveRecordShards::DefaultReplicaPatches::ActiveRelationPatches)
|
27
28
|
ActiveRecord::Associations::CollectionProxy.include(ActiveRecordShards::AssociationCollectionConnectionSelection)
|
28
|
-
ActiveRecord::Associations::Builder::HasAndBelongsToMany.include(ActiveRecordShards::
|
29
|
+
ActiveRecord::Associations::Builder::HasAndBelongsToMany.include(ActiveRecordShards::DefaultReplicaPatches::Rails41HasAndBelongsToManyBuilderExtension)
|
29
30
|
ActiveRecord::SchemaDumper.prepend(ActiveRecordShards::SchemaDumperExtension)
|
30
31
|
|
31
32
|
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
32
33
|
when '4.2'
|
33
34
|
require 'active_record_shards/patches-4-2'
|
34
|
-
|
35
|
-
|
35
|
+
|
36
|
+
# https://github.com/rails/rails/blob/v4.2.11.3/activerecord/lib/active_record/associations/association.rb#L97
|
37
|
+
ActiveRecord::Associations::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationAssociationScopePatch)
|
38
|
+
|
39
|
+
# https://github.com/rails/rails/blob/v4.2.11.3/activerecord/lib/active_record/associations/singular_association.rb#L44-L53
|
40
|
+
ActiveRecord::Associations::SingularAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationGetRecordsPatch)
|
41
|
+
|
42
|
+
# https://github.com/rails/rails/blob/v4.2.11.3/activerecord/lib/active_record/associations/collection_association.rb#L447-L456
|
43
|
+
ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationGetRecordsPatch)
|
44
|
+
|
45
|
+
# https://github.com/rails/rails/blob/v4.2.11.3/activerecord/lib/active_record/associations/preloader/association.rb#L89
|
46
|
+
ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsPreloaderAssociationAssociatedRecordsByOwnerPatch)
|
47
|
+
when '5.0'
|
48
|
+
# https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/associations/association.rb#L97
|
49
|
+
ActiveRecord::Associations::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationAssociationScopePatch)
|
50
|
+
|
51
|
+
# https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/associations/singular_association.rb#L56-L65
|
52
|
+
ActiveRecord::Associations::SingularAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationGetRecordsPatch)
|
53
|
+
|
54
|
+
# https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/associations/collection_association.rb#L442-L451
|
55
|
+
ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationGetRecordsPatch)
|
56
|
+
|
57
|
+
# https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/associations/preloader/association.rb#L120
|
58
|
+
ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsPreloaderAssociationLoadRecordsPatch)
|
59
|
+
when '5.1'
|
60
|
+
# https://github.com/rails/rails/blob/v5.1.7/activerecord/lib/active_record/associations/association.rb#L97
|
61
|
+
ActiveRecord::Associations::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationAssociationScopePatch)
|
62
|
+
|
63
|
+
# https://github.com/rails/rails/blob/v5.1.7/activerecord/lib/active_record/associations/singular_association.rb#L41
|
64
|
+
ActiveRecord::Associations::SingularAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationFindTargetPatch)
|
65
|
+
|
66
|
+
# https://github.com/rails/rails/blob/v5.1.7/activerecord/lib/active_record/associations/collection_association.rb#L305
|
67
|
+
ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationFindTargetPatch)
|
68
|
+
|
69
|
+
# https://github.com/rails/rails/blob/v5.1.7/activerecord/lib/active_record/associations/preloader/association.rb#L120
|
70
|
+
ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsPreloaderAssociationLoadRecordsPatch)
|
71
|
+
when '5.2'
|
72
|
+
# https://github.com/rails/rails/blob/v5.2.6/activerecord/lib/active_record/relation.rb#L530
|
73
|
+
# But the #exec_queries method also calls #connection, and I don't know if we should patch that one, too...
|
74
|
+
ActiveRecord::Relation.prepend(ActiveRecordShards::DefaultReplicaPatches::Rails52RelationPatches)
|
75
|
+
|
76
|
+
# https://github.com/rails/rails/blob/v5.2.6/activerecord/lib/active_record/associations/singular_association.rb#L42
|
77
|
+
ActiveRecord::Associations::SingularAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationFindTargetPatch)
|
78
|
+
|
79
|
+
# https://github.com/rails/rails/blob/v5.2.6/activerecord/lib/active_record/associations/collection_association.rb#L308
|
80
|
+
ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationFindTargetPatch)
|
81
|
+
|
82
|
+
# https://github.com/rails/rails/blob/v5.2.6/activerecord/lib/active_record/associations/preloader/association.rb#L96
|
83
|
+
ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsPreloaderAssociationLoadRecordsPatch)
|
84
|
+
when '6.0'
|
85
|
+
# https://github.com/rails/rails/blob/v6.0.4/activerecord/lib/active_record/type_caster/connection.rb#L28
|
86
|
+
ActiveRecord::TypeCaster::Connection.prepend(ActiveRecordShards::DefaultReplicaPatches::TypeCasterConnectionConnectionPatch)
|
87
|
+
|
88
|
+
# https://github.com/rails/rails/blob/v6.0.4/activerecord/lib/active_record/schema.rb#L53-L54
|
89
|
+
ActiveRecord::Schema.prepend(ActiveRecordShards::DefaultReplicaPatches::SchemaDefinePatch)
|
90
|
+
|
91
|
+
# https://github.com/rails/rails/blob/v6.0.4/activerecord/lib/active_record/relation.rb#L739
|
92
|
+
# But the #exec_queries and #compute_cache_version methods also call #connection, and I don't know if we should patch those, too...
|
93
|
+
ActiveRecord::Relation.prepend(ActiveRecordShards::DefaultReplicaPatches::Rails52RelationPatches)
|
94
|
+
|
95
|
+
# https://github.com/rails/rails/blob/v6.0.4/activerecord/lib/active_record/associations/association.rb#L213
|
96
|
+
ActiveRecord::Associations::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationFindTargetPatch)
|
36
97
|
else
|
37
98
|
raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
|
38
99
|
end
|
100
|
+
|
101
|
+
require 'active_record_shards/deprecation'
|
102
|
+
|
103
|
+
ActiveRecordShards::Deprecation.deprecate_methods(
|
104
|
+
ActiveRecordShards::AssociationCollectionConnectionSelection,
|
105
|
+
on_slave_if: :on_replica_if,
|
106
|
+
on_slave_unless: :on_replica_unless,
|
107
|
+
on_slave: :on_replica,
|
108
|
+
on_master: :on_primary,
|
109
|
+
on_master_if: :on_primary_if,
|
110
|
+
on_master_unless: :on_primary_unless
|
111
|
+
)
|
112
|
+
|
113
|
+
ActiveRecordShards::Deprecation.deprecate_methods(
|
114
|
+
ActiveRecordShards::ConnectionSwitcher,
|
115
|
+
on_slave_if: :on_replica_if,
|
116
|
+
on_slave_unless: :on_replica_unless,
|
117
|
+
on_master_or_slave: :on_primary_or_replica,
|
118
|
+
on_slave: :on_replica,
|
119
|
+
on_master: :on_primary,
|
120
|
+
on_master_if: :on_primary_if,
|
121
|
+
on_master_unless: :on_primary_unless,
|
122
|
+
on_slave?: :on_replica?
|
123
|
+
)
|
124
|
+
|
125
|
+
ActiveRecordShards::Deprecation.deprecate_methods(
|
126
|
+
ActiveRecordShards::DefaultReplicaPatches,
|
127
|
+
transaction_with_slave_off: :transaction_with_replica_off,
|
128
|
+
on_slave_unless_tx: :on_replica_unless_tx
|
129
|
+
)
|
130
|
+
|
131
|
+
ActiveRecordShards::Deprecation.deprecate_methods(
|
132
|
+
ActiveRecordShards::Model,
|
133
|
+
on_slave_by_default?: :on_replica_by_default?,
|
134
|
+
:on_slave_by_default= => :on_replica_by_default=
|
135
|
+
)
|
136
|
+
|
137
|
+
ActiveRecordShards::Deprecation.deprecate_methods(
|
138
|
+
ActiveRecordShards::ShardSelection,
|
139
|
+
on_slave?: :on_replica?,
|
140
|
+
:on_slave= => :on_replica=
|
141
|
+
)
|
@@ -1,40 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module ActiveRecordShards
|
3
4
|
module AssociationCollectionConnectionSelection
|
4
|
-
def
|
5
|
-
condition ?
|
5
|
+
def on_replica_if(condition)
|
6
|
+
condition ? on_replica : self
|
6
7
|
end
|
8
|
+
alias_method :on_slave_if, :on_replica_if
|
7
9
|
|
8
|
-
def
|
9
|
-
|
10
|
+
def on_replica_unless(condition)
|
11
|
+
on_replica_if(!condition)
|
10
12
|
end
|
13
|
+
alias_method :on_slave_unless, :on_replica_unless
|
11
14
|
|
12
|
-
def
|
13
|
-
condition ?
|
15
|
+
def on_primary_if(condition)
|
16
|
+
condition ? on_primary : self
|
14
17
|
end
|
18
|
+
alias_method :on_master_if, :on_primary_if
|
15
19
|
|
16
|
-
def
|
17
|
-
|
20
|
+
def on_primary_unless(condition)
|
21
|
+
on_primary_if(!condition)
|
18
22
|
end
|
23
|
+
alias_method :on_master_unless, :on_primary_unless
|
19
24
|
|
20
|
-
def
|
21
|
-
|
25
|
+
def on_replica
|
26
|
+
PrimaryReplicaProxy.new(self, :replica)
|
22
27
|
end
|
28
|
+
alias_method :on_slave, :on_replica
|
23
29
|
|
24
|
-
def
|
25
|
-
|
30
|
+
def on_primary
|
31
|
+
PrimaryReplicaProxy.new(self, :primary)
|
26
32
|
end
|
33
|
+
alias_method :on_master, :on_primary
|
27
34
|
|
28
|
-
class
|
35
|
+
class PrimaryReplicaProxy
|
29
36
|
def initialize(association_collection, which)
|
30
37
|
@association_collection = association_collection
|
31
38
|
@which = which
|
32
39
|
end
|
33
40
|
|
34
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/
|
41
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
|
35
42
|
reflection = @association_collection.proxy_association.reflection
|
36
43
|
reflection.klass.on_cx_switch_block(@which) { @association_collection.send(method, *args, &block) }
|
37
44
|
end
|
38
45
|
end
|
46
|
+
|
47
|
+
MasterSlaveProxy = PrimaryReplicaProxy
|
39
48
|
end
|
40
49
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'active_support/core_ext'
|
3
4
|
|
4
5
|
module ActiveRecordShards
|
@@ -6,7 +7,7 @@ module ActiveRecordShards
|
|
6
7
|
module_function
|
7
8
|
|
8
9
|
def explode(conf)
|
9
|
-
conf = conf.deep_dup
|
10
|
+
conf = conf.to_h.deep_dup
|
10
11
|
|
11
12
|
conf.to_a.each do |env_name, env_config|
|
12
13
|
next unless shards = env_config.delete('shards')
|
@@ -23,10 +24,18 @@ module ActiveRecordShards
|
|
23
24
|
end
|
24
25
|
|
25
26
|
conf.to_a.each do |env_name, env_config|
|
26
|
-
if
|
27
|
-
expand_child!(env_config,
|
28
|
-
conf["#{env_name}
|
27
|
+
if replica_conf = env_config.delete('replica')
|
28
|
+
expand_child!(env_config, replica_conf)
|
29
|
+
conf["#{env_name}_replica"] = replica_conf
|
30
|
+
end
|
31
|
+
|
32
|
+
# rubocop:disable Style/Next
|
33
|
+
if legacy_replica_conf = env_config.delete('slave')
|
34
|
+
ActiveRecordShards::Deprecation.warn('`slave` configuration keys should be replaced with `replica` keys!')
|
35
|
+
expand_child!(env_config, legacy_replica_conf)
|
36
|
+
conf["#{env_name}_replica"] = legacy_replica_conf
|
29
37
|
end
|
38
|
+
# rubocop:enable Style/Next
|
30
39
|
end
|
31
40
|
|
32
41
|
conf
|
@@ -34,7 +43,7 @@ module ActiveRecordShards
|
|
34
43
|
|
35
44
|
def expand_child!(parent, child)
|
36
45
|
parent.each do |key, value|
|
37
|
-
unless ['slave', 'shards'].include?(key) || value.is_a?(Hash)
|
46
|
+
unless ['slave', 'replica', 'shards'].include?(key) || value.is_a?(Hash)
|
38
47
|
child[key] ||= value
|
39
48
|
end
|
40
49
|
end
|
@@ -4,8 +4,8 @@ module ActiveRecordShards
|
|
4
4
|
def connection_pool_name # :nodoc:
|
5
5
|
name = current_shard_selection.shard_name(self)
|
6
6
|
|
7
|
-
# e.g. if "
|
8
|
-
if configurations[name].nil? &&
|
7
|
+
# e.g. if "production_replica" is not defined in `Configuration`, fall back to "production"
|
8
|
+
if configurations[name].nil? && on_replica?
|
9
9
|
current_shard_selection.shard_name(self, false)
|
10
10
|
else
|
11
11
|
name
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'active_record_shards/shard_support'
|
3
4
|
|
4
5
|
module ActiveRecordShards
|
5
6
|
module ConnectionSwitcher
|
6
|
-
SHARD_NAMES_CONFIG_KEY = 'shard_names'
|
7
|
+
SHARD_NAMES_CONFIG_KEY = 'shard_names'
|
7
8
|
|
8
9
|
def self.extended(base)
|
9
10
|
if ActiveRecord::VERSION::MAJOR >= 5
|
@@ -23,6 +24,10 @@ module ActiveRecordShards
|
|
23
24
|
switch_connection(shard: new_default_shard)
|
24
25
|
end
|
25
26
|
|
27
|
+
def on_primary_db(&block)
|
28
|
+
on_shard(nil, &block)
|
29
|
+
end
|
30
|
+
|
26
31
|
def on_shard(shard)
|
27
32
|
old_options = current_shard_selection.options
|
28
33
|
switch_connection(shard: shard) if supports_sharding?
|
@@ -31,9 +36,9 @@ module ActiveRecordShards
|
|
31
36
|
switch_connection(old_options)
|
32
37
|
end
|
33
38
|
|
34
|
-
def on_first_shard
|
39
|
+
def on_first_shard(&block)
|
35
40
|
shard_name = shard_names.first
|
36
|
-
on_shard(shard_name)
|
41
|
+
on_shard(shard_name, &block)
|
37
42
|
end
|
38
43
|
|
39
44
|
def shards
|
@@ -54,70 +59,79 @@ module ActiveRecordShards
|
|
54
59
|
switch_connection(old_options)
|
55
60
|
end
|
56
61
|
|
57
|
-
def
|
58
|
-
condition ?
|
62
|
+
def on_replica_if(condition, &block)
|
63
|
+
condition ? on_replica(&block) : yield
|
59
64
|
end
|
65
|
+
alias_method :on_slave_if, :on_replica_if
|
60
66
|
|
61
|
-
def
|
62
|
-
|
67
|
+
def on_replica_unless(condition, &block)
|
68
|
+
on_replica_if(!condition, &block)
|
63
69
|
end
|
70
|
+
alias_method :on_slave_unless, :on_replica_unless
|
64
71
|
|
65
|
-
def
|
66
|
-
condition ?
|
72
|
+
def on_primary_if(condition, &block)
|
73
|
+
condition ? on_primary(&block) : yield
|
67
74
|
end
|
75
|
+
alias_method :on_master_if, :on_primary_if
|
68
76
|
|
69
|
-
def
|
70
|
-
|
77
|
+
def on_primary_unless(condition, &block)
|
78
|
+
on_primary_if(!condition, &block)
|
71
79
|
end
|
80
|
+
alias_method :on_master_unless, :on_primary_unless
|
72
81
|
|
73
|
-
def
|
82
|
+
def on_primary_or_replica(which, &block)
|
74
83
|
if block_given?
|
75
84
|
on_cx_switch_block(which, &block)
|
76
85
|
else
|
77
|
-
|
86
|
+
PrimaryReplicaProxy.new(self, which)
|
78
87
|
end
|
79
88
|
end
|
89
|
+
alias_method :on_master_or_slave, :on_primary_or_replica
|
80
90
|
|
81
|
-
# Executes queries using the
|
82
|
-
# if you want to execute a block of code on the
|
83
|
-
# Account.
|
91
|
+
# Executes queries using the replica database. Fails over to primary if no replica is found.
|
92
|
+
# if you want to execute a block of code on the replica you can go:
|
93
|
+
# Account.on_replica do
|
84
94
|
# Account.first
|
85
95
|
# end
|
86
|
-
# the first account will be found on the
|
96
|
+
# the first account will be found on the replica DB
|
87
97
|
#
|
88
98
|
# For one-liners you can simply do
|
89
|
-
# Account.
|
90
|
-
def
|
91
|
-
|
99
|
+
# Account.on_replica.first
|
100
|
+
def on_replica(&block)
|
101
|
+
on_primary_or_replica(:replica, &block)
|
92
102
|
end
|
103
|
+
alias_method :on_slave, :on_replica
|
93
104
|
|
94
|
-
def
|
95
|
-
|
105
|
+
def on_primary(&block)
|
106
|
+
on_primary_or_replica(:primary, &block)
|
96
107
|
end
|
108
|
+
alias_method :on_master, :on_primary
|
97
109
|
|
98
110
|
# just to ease the transition from replica to active_record_shards
|
99
|
-
alias_method :with_slave, :
|
100
|
-
alias_method :with_slave_if, :
|
101
|
-
alias_method :with_slave_unless, :
|
111
|
+
alias_method :with_slave, :on_replica
|
112
|
+
alias_method :with_slave_if, :on_replica_if
|
113
|
+
alias_method :with_slave_unless, :on_replica_unless
|
102
114
|
|
103
115
|
def on_cx_switch_block(which, force: false, construct_ro_scope: nil, &block)
|
104
|
-
@
|
105
|
-
@
|
116
|
+
@disallow_replica ||= 0
|
117
|
+
@disallow_replica += 1 if [:primary, :master].include?(which)
|
118
|
+
|
119
|
+
ActiveRecordShards::Deprecation.warn('the `:master` option should be replaced with `:primary`!') if which == :master
|
106
120
|
|
107
|
-
|
121
|
+
switch_to_replica = force || @disallow_replica.zero?
|
108
122
|
old_options = current_shard_selection.options
|
109
123
|
|
110
|
-
switch_connection(
|
124
|
+
switch_connection(replica: switch_to_replica)
|
111
125
|
|
112
126
|
# we avoid_readonly_scope to prevent some stack overflow problems, like when
|
113
127
|
# .columns calls .with_scope which calls .columns and onward, endlessly.
|
114
|
-
if self == ActiveRecord::Base || !
|
128
|
+
if self == ActiveRecord::Base || !switch_to_replica || construct_ro_scope == false
|
115
129
|
yield
|
116
130
|
else
|
117
131
|
readonly.scoping(&block)
|
118
132
|
end
|
119
133
|
ensure
|
120
|
-
@
|
134
|
+
@disallow_replica -= 1 if [:primary, :master].include?(which)
|
121
135
|
switch_connection(old_options) if old_options
|
122
136
|
end
|
123
137
|
|
@@ -125,9 +139,10 @@ module ActiveRecordShards
|
|
125
139
|
shard_names.any?
|
126
140
|
end
|
127
141
|
|
128
|
-
def
|
129
|
-
current_shard_selection.
|
142
|
+
def on_replica?
|
143
|
+
current_shard_selection.on_replica?
|
130
144
|
end
|
145
|
+
alias_method :on_slave?, :on_replica?
|
131
146
|
|
132
147
|
def current_shard_selection
|
133
148
|
Thread.current[:shard_selection] ||= ShardSelection.new
|
@@ -144,6 +159,7 @@ module ActiveRecordShards
|
|
144
159
|
unless config.fetch(SHARD_NAMES_CONFIG_KEY, []).all? { |shard_name| shard_name.is_a?(Integer) }
|
145
160
|
raise "All shard names must be integers: #{config[SHARD_NAMES_CONFIG_KEY].inspect}."
|
146
161
|
end
|
162
|
+
|
147
163
|
config[SHARD_NAMES_CONFIG_KEY] || []
|
148
164
|
end
|
149
165
|
|
@@ -151,14 +167,15 @@ module ActiveRecordShards
|
|
151
167
|
|
152
168
|
def switch_connection(options)
|
153
169
|
if options.any?
|
154
|
-
if options.key?(:
|
155
|
-
current_shard_selection.
|
170
|
+
if options.key?(:replica)
|
171
|
+
current_shard_selection.on_replica = options[:replica]
|
156
172
|
end
|
157
173
|
|
158
174
|
if options.key?(:shard)
|
159
175
|
unless configurations[shard_env]
|
160
176
|
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.keys.inspect})"
|
161
177
|
end
|
178
|
+
|
162
179
|
current_shard_selection.shard = options[:shard]
|
163
180
|
end
|
164
181
|
|
@@ -170,9 +187,11 @@ module ActiveRecordShards
|
|
170
187
|
ActiveRecordShards.rails_env
|
171
188
|
end
|
172
189
|
|
173
|
-
|
190
|
+
# Make these few schema related methods available before having switched to
|
191
|
+
# a shard.
|
192
|
+
def with_default_shard(&block)
|
174
193
|
if is_sharded? && current_shard_id.nil? && table_name != ActiveRecord::SchemaMigration.table_name
|
175
|
-
on_first_shard
|
194
|
+
on_first_shard(&block)
|
176
195
|
else
|
177
196
|
yield
|
178
197
|
end
|
@@ -192,25 +211,27 @@ module ActiveRecordShards
|
|
192
211
|
with_default_shard { table_exists_without_default_shard? }
|
193
212
|
end
|
194
213
|
|
195
|
-
class
|
214
|
+
class PrimaryReplicaProxy
|
196
215
|
def initialize(target, which)
|
197
216
|
@target = target
|
198
217
|
@which = which
|
199
218
|
end
|
200
219
|
|
201
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/
|
202
|
-
@target.
|
220
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
|
221
|
+
@target.on_primary_or_replica(@which) { @target.send(method, *args, &block) }
|
203
222
|
end
|
204
223
|
end
|
224
|
+
|
225
|
+
MasterSlaveProxy = PrimaryReplicaProxy
|
205
226
|
end
|
206
227
|
end
|
207
228
|
|
208
229
|
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
209
230
|
when '4.2'
|
210
|
-
require 'active_record_shards/connection_switcher-4-
|
231
|
+
require 'active_record_shards/connection_switcher-4-2'
|
211
232
|
when '5.0'
|
212
233
|
require 'active_record_shards/connection_switcher-5-0'
|
213
|
-
when '5.1', '5.2'
|
234
|
+
when '5.1', '5.2', '6.0'
|
214
235
|
require 'active_record_shards/connection_switcher-5-1'
|
215
236
|
else
|
216
237
|
raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
|