active_record_shards 4.0.0.beta9 → 5.0.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 +126 -57
- data/lib/active_record_shards/association_collection_connection_selection.rb +16 -14
- data/lib/active_record_shards/configuration_parser.rb +6 -5
- data/lib/active_record_shards/{connection_switcher_5_1.rb → connection_switcher-5-1.rb} +11 -1
- data/lib/active_record_shards/connection_switcher-6-0.rb +30 -0
- data/lib/active_record_shards/connection_switcher.rb +131 -46
- data/lib/active_record_shards/default_replica_patches.rb +237 -0
- data/lib/active_record_shards/migration.rb +124 -0
- data/lib/active_record_shards/model.rb +36 -21
- data/lib/active_record_shards/schema_dumper_extension.rb +42 -0
- data/lib/active_record_shards/shard_selection.rb +47 -7
- data/lib/active_record_shards/shard_support.rb +7 -7
- data/lib/active_record_shards/sql_comments.rb +11 -4
- data/lib/active_record_shards/tasks.rb +46 -29
- data/lib/active_record_shards.rb +59 -12
- metadata +64 -52
- data/lib/active_record_shards/base_config.rb +0 -26
- data/lib/active_record_shards/connection_resolver.rb +0 -31
- data/lib/active_record_shards/connection_switcher_5_0.rb +0 -19
- data/lib/active_record_shards/default_slave_patches.rb +0 -136
- data/lib/active_record_shards/no_shard_selection.rb +0 -18
- data/lib/active_record_shards/sharded_model.rb +0 -31
- data/lib/active_record_shards/slave_db.rb +0 -105
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 07a2ae4e9b397abcc455655266c9d66c76af160e56ea963b401a130589f3f48c
|
|
4
|
+
data.tar.gz: 45993e917fb0585637c72f89d6bee77f805e0192efb6ca3f476e86929d9192ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16309006259fadb5b3851ea470d8859618c2df4d10555e5ecb50c257d538ac3f36c69490a33547bc7e53e6a2962d5985623176021b5f0866f9a35973138000cc
|
|
7
|
+
data.tar.gz: d23f2ae22180be953ba4a8bbb23000f73901ecf7950cae95858d371eec24bf01f2e7d6ee0c7225e8bbe2f76490261baae4059193718ae3cb17ffd78ab338f852
|
data/README.md
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
[](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 5.0 and has in some form or another been used in production on
|
|
8
|
+
ActiveRecord Shards has been used and tested on Rails 5.x and 6.0, and has in some form or another been used in production on large Rails apps for several years.
|
|
9
|
+
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Configuration](#configuration)
|
|
12
|
+
- [Migrations](#migrations)
|
|
13
|
+
- [Example](#example)
|
|
14
|
+
- [Shared Model](#create-a-table-for-the-shared-not-sharded-model)
|
|
15
|
+
- [Sharded Model](#create-a-table-for-the-sharded-model)
|
|
16
|
+
- [Usage](#usage)
|
|
17
|
+
- [Debugging](#debugging)
|
|
9
18
|
|
|
10
19
|
## Installation
|
|
11
20
|
|
|
@@ -15,83 +24,143 @@ and make sure to require 'active\_record\_shards' in some way.
|
|
|
15
24
|
|
|
16
25
|
## Configuration
|
|
17
26
|
|
|
18
|
-
Add the
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
Add the replica and shard configuration to config/database.yml:
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
production:
|
|
31
|
+
adapter: mysql
|
|
32
|
+
encoding: utf8
|
|
33
|
+
database: my_app_main
|
|
34
|
+
pool: 5
|
|
35
|
+
host: db1
|
|
36
|
+
username: root
|
|
37
|
+
password:
|
|
38
|
+
replica:
|
|
39
|
+
host: db1_replica
|
|
40
|
+
shards:
|
|
41
|
+
1:
|
|
42
|
+
host: db_shard1
|
|
43
|
+
database: my_app_shard
|
|
44
|
+
replica:
|
|
45
|
+
host: db_shard1_replica
|
|
46
|
+
2:
|
|
47
|
+
host: db_shard2
|
|
48
|
+
database: my_app_shard
|
|
49
|
+
replica:
|
|
50
|
+
host: db_shard2_replica
|
|
51
|
+
```
|
|
41
52
|
|
|
42
53
|
basically connections inherit configuration from the parent configuration file.
|
|
43
54
|
|
|
44
|
-
##
|
|
55
|
+
## Migrations
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
ActiveRecord Shards also patches migrations to support running migrations on a shared (not sharded) or a sharded database.
|
|
58
|
+
Each migration class has to specify a shard spec indicating where to run the migration.
|
|
59
|
+
|
|
60
|
+
Valid shard specs:
|
|
61
|
+
|
|
62
|
+
* `:none` - Run this migration on the shared database, not any shards
|
|
63
|
+
* `:all` - Run this migration on all of the shards, not the shared database
|
|
64
|
+
|
|
65
|
+
#### Example
|
|
66
|
+
|
|
67
|
+
###### Create a table for the shared (not sharded) model
|
|
48
68
|
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
```ruby
|
|
70
|
+
class CreateAccounts < ActiveRecord::Migration
|
|
71
|
+
shard :none
|
|
72
|
+
|
|
73
|
+
def change
|
|
74
|
+
create_table :accounts do |t|
|
|
75
|
+
# This is NOT necessary for the gem to work, we just use it in the examples below demonstrating one way to switch shards
|
|
76
|
+
t.integer :shard_id, null: false
|
|
77
|
+
|
|
78
|
+
t.string :name
|
|
51
79
|
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
###### Create a table for the sharded model
|
|
52
85
|
|
|
53
|
-
|
|
54
|
-
|
|
86
|
+
```ruby
|
|
87
|
+
class CreateProjects < ActiveRecord::Migration
|
|
88
|
+
shard :all
|
|
89
|
+
|
|
90
|
+
def change
|
|
91
|
+
create_table :projects do |t|
|
|
92
|
+
t.references :account
|
|
93
|
+
t.string :name
|
|
55
94
|
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
Normally you have some models that live on a shared database, and you might need to query this data in order to know what shard to switch to.
|
|
102
|
+
All the models that live on the shared database must be marked as not\_sharded:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
class Account < ActiveRecord::Base
|
|
106
|
+
not_sharded
|
|
107
|
+
|
|
108
|
+
has_many :projects
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class Project < ActiveRecord::Base
|
|
112
|
+
belongs_to :account
|
|
113
|
+
end
|
|
114
|
+
```
|
|
56
115
|
|
|
57
116
|
So in this setup the accounts live on the shared database, but the projects are sharded. If accounts have a shard\_id column, you could lookup the account
|
|
58
117
|
in a rack middleware and switch to the right shard:
|
|
59
118
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def call(env)
|
|
66
|
-
account = lookup_account(env)
|
|
119
|
+
```ruby
|
|
120
|
+
class AccountMiddleware
|
|
121
|
+
def initialize(app)
|
|
122
|
+
@app = app
|
|
123
|
+
end
|
|
67
124
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@app.call(env)
|
|
71
|
-
end
|
|
72
|
-
else
|
|
73
|
-
@app.call(env)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
125
|
+
def call(env)
|
|
126
|
+
account = lookup_account(env)
|
|
76
127
|
|
|
77
|
-
|
|
78
|
-
|
|
128
|
+
if account
|
|
129
|
+
ActiveRecord::Base.on_shard(account.shard_id) do
|
|
130
|
+
@app.call(env)
|
|
79
131
|
end
|
|
132
|
+
else
|
|
133
|
+
@app.call(env)
|
|
80
134
|
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def lookup_account(env)
|
|
138
|
+
# ...
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
```
|
|
81
142
|
|
|
82
|
-
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:
|
|
83
144
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
145
|
+
```ruby
|
|
146
|
+
ActiveRecord::Base.on_replica do
|
|
147
|
+
Account.find_by_big_expensive_query
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
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
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
Account.on_replica.find_by_big_expensive_query
|
|
155
|
+
```
|
|
87
156
|
|
|
88
|
-
|
|
157
|
+
If you do not want instances returned from replicas to be marked as read-only, this can be disabled globally:
|
|
89
158
|
|
|
90
|
-
|
|
159
|
+
`ActiveRecordShards.disable_replica_readonly_records = true`
|
|
91
160
|
|
|
92
161
|
## Debugging
|
|
93
162
|
|
|
94
|
-
Show if a query went to
|
|
163
|
+
Show if a query went to primary or replica in the logs:
|
|
95
164
|
|
|
96
165
|
```Ruby
|
|
97
166
|
require 'active_record_shards/sql_comments'
|
|
@@ -1,40 +1,42 @@
|
|
|
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
|
|
7
8
|
|
|
8
|
-
def
|
|
9
|
-
|
|
9
|
+
def on_replica_unless(condition)
|
|
10
|
+
on_replica_if(!condition)
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
def
|
|
13
|
-
condition ?
|
|
13
|
+
def on_primary_if(condition)
|
|
14
|
+
condition ? on_primary : self
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
def
|
|
17
|
-
|
|
17
|
+
def on_primary_unless(condition)
|
|
18
|
+
on_primary_if(!condition)
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
def
|
|
21
|
-
|
|
21
|
+
def on_replica
|
|
22
|
+
PrimaryReplicaProxy.new(self, :replica)
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
def
|
|
25
|
-
|
|
25
|
+
def on_primary
|
|
26
|
+
PrimaryReplicaProxy.new(self, :primary)
|
|
26
27
|
end
|
|
27
28
|
|
|
28
|
-
class
|
|
29
|
+
class PrimaryReplicaProxy
|
|
29
30
|
def initialize(association_collection, which)
|
|
30
31
|
@association_collection = association_collection
|
|
31
32
|
@which = which
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/
|
|
35
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
|
|
35
36
|
reflection = @association_collection.proxy_association.reflection
|
|
36
37
|
reflection.klass.on_cx_switch_block(@which) { @association_collection.send(method, *args, &block) }
|
|
37
38
|
end
|
|
39
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
|
38
40
|
end
|
|
39
41
|
end
|
|
40
42
|
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,9 +24,9 @@ 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
|
|
29
30
|
end
|
|
30
31
|
end
|
|
31
32
|
|
|
@@ -34,7 +35,7 @@ module ActiveRecordShards
|
|
|
34
35
|
|
|
35
36
|
def expand_child!(parent, child)
|
|
36
37
|
parent.each do |key, value|
|
|
37
|
-
unless ['
|
|
38
|
+
unless ['replica', 'shards'].include?(key) || value.is_a?(Hash)
|
|
38
39
|
child[key] ||= value
|
|
39
40
|
end
|
|
40
41
|
end
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
module ActiveRecordShards
|
|
2
|
-
module
|
|
2
|
+
module ConnectionSwitcher
|
|
3
|
+
def connection_specification_name
|
|
4
|
+
name = current_shard_selection.resolve_connection_name(sharded: is_sharded?, configurations: configurations)
|
|
5
|
+
|
|
6
|
+
unless configurations[name] || name == "primary"
|
|
7
|
+
raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.to_h.keys.inspect})"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
name
|
|
11
|
+
end
|
|
12
|
+
|
|
3
13
|
private
|
|
4
14
|
|
|
5
15
|
def ensure_shard_connection
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module ActiveRecordShards
|
|
2
|
+
module ConnectionSwitcher
|
|
3
|
+
def connection_specification_name
|
|
4
|
+
name = current_shard_selection.resolve_connection_name(sharded: is_sharded?, configurations: configurations)
|
|
5
|
+
|
|
6
|
+
@_ars_connection_specification_names ||= {}
|
|
7
|
+
unless @_ars_connection_specification_names.include?(name)
|
|
8
|
+
unless configurations[name] || name == "primary"
|
|
9
|
+
raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.to_h.keys.inspect})"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
@_ars_connection_specification_names[name] = true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
name
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def ensure_shard_connection
|
|
21
|
+
# See if we've connected before. If not, call `#establish_connection`
|
|
22
|
+
# so that ActiveRecord can resolve connection_specification_name to an
|
|
23
|
+
# ARS connection.
|
|
24
|
+
spec_name = connection_specification_name
|
|
25
|
+
|
|
26
|
+
pool = connection_handler.retrieve_connection_pool(spec_name)
|
|
27
|
+
connection_handler.establish_connection(spec_name.to_sym) if pool.nil?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -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
|
base.singleton_class.send(:alias_method, :load_schema_without_default_shard!, :load_schema!)
|
|
@@ -13,17 +14,26 @@ module ActiveRecordShards
|
|
|
13
14
|
base.singleton_class.send(:alias_method, :table_exists?, :table_exists_with_default_shard?)
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
def default_shard=(new_default_shard)
|
|
18
|
+
ActiveRecordShards::ShardSelection.default_shard = new_default_shard
|
|
19
|
+
switch_connection(shard: new_default_shard)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def on_primary_db(&block)
|
|
23
|
+
on_shard(nil, &block)
|
|
24
|
+
end
|
|
25
|
+
|
|
16
26
|
def on_shard(shard)
|
|
17
|
-
|
|
18
|
-
switch_connection(
|
|
27
|
+
old_options = current_shard_selection.options
|
|
28
|
+
switch_connection(shard: shard) if supports_sharding?
|
|
19
29
|
yield
|
|
20
30
|
ensure
|
|
21
|
-
switch_connection(
|
|
31
|
+
switch_connection(old_options)
|
|
22
32
|
end
|
|
23
33
|
|
|
24
|
-
def on_first_shard
|
|
34
|
+
def on_first_shard(&block)
|
|
25
35
|
shard_name = shard_names.first
|
|
26
|
-
on_shard(shard_name)
|
|
36
|
+
on_shard(shard_name, &block)
|
|
27
37
|
end
|
|
28
38
|
|
|
29
39
|
def shards
|
|
@@ -31,85 +41,144 @@ module ActiveRecordShards
|
|
|
31
41
|
end
|
|
32
42
|
|
|
33
43
|
def on_all_shards
|
|
34
|
-
|
|
44
|
+
old_options = current_shard_selection.options
|
|
35
45
|
if supports_sharding?
|
|
36
46
|
shard_names.map do |shard|
|
|
37
|
-
switch_connection(
|
|
47
|
+
switch_connection(shard: shard)
|
|
38
48
|
yield(shard)
|
|
39
49
|
end
|
|
40
50
|
else
|
|
41
51
|
[yield]
|
|
42
52
|
end
|
|
43
53
|
ensure
|
|
44
|
-
switch_connection(
|
|
54
|
+
switch_connection(old_options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def on_replica_if(condition, &block)
|
|
58
|
+
condition ? on_replica(&block) : yield
|
|
45
59
|
end
|
|
46
60
|
|
|
47
|
-
def
|
|
48
|
-
|
|
61
|
+
def on_replica_unless(condition, &block)
|
|
62
|
+
on_replica_if(!condition, &block)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def on_primary_if(condition, &block)
|
|
66
|
+
condition ? on_primary(&block) : yield
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def on_primary_unless(condition, &block)
|
|
70
|
+
on_primary_if(!condition, &block)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def on_primary_or_replica(which, &block)
|
|
74
|
+
if block_given?
|
|
75
|
+
on_cx_switch_block(which, &block)
|
|
76
|
+
else
|
|
77
|
+
PrimaryReplicaProxy.new(self, which)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Executes queries using the replica database. Fails over to primary if no replica is found.
|
|
82
|
+
# if you want to execute a block of code on the replica you can go:
|
|
83
|
+
# Account.on_replica do
|
|
84
|
+
# Account.first
|
|
85
|
+
# end
|
|
86
|
+
# the first account will be found on the replica DB
|
|
87
|
+
#
|
|
88
|
+
# For one-liners you can simply do
|
|
89
|
+
# Account.on_replica.first
|
|
90
|
+
def on_replica(&block)
|
|
91
|
+
on_primary_or_replica(:replica, &block)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def on_primary(&block)
|
|
95
|
+
on_primary_or_replica(:primary, &block)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def on_cx_switch_block(which, force: false, construct_ro_scope: nil, &block)
|
|
99
|
+
@disallow_replica ||= 0
|
|
100
|
+
@disallow_replica += 1 if which == :primary
|
|
101
|
+
|
|
102
|
+
switch_to_replica = force || @disallow_replica.zero?
|
|
103
|
+
old_options = current_shard_selection.options
|
|
104
|
+
|
|
105
|
+
switch_connection(replica: switch_to_replica)
|
|
106
|
+
|
|
107
|
+
# we avoid_readonly_scope to prevent some stack overflow problems, like when
|
|
108
|
+
# .columns calls .with_scope which calls .columns and onward, endlessly.
|
|
109
|
+
if self == ActiveRecord::Base || !switch_to_replica || construct_ro_scope == false || ActiveRecordShards.disable_replica_readonly_records == true
|
|
110
|
+
yield
|
|
111
|
+
else
|
|
112
|
+
readonly.scoping(&block)
|
|
113
|
+
end
|
|
114
|
+
ensure
|
|
115
|
+
@disallow_replica -= 1 if which == :primary
|
|
116
|
+
switch_connection(old_options) if old_options
|
|
49
117
|
end
|
|
50
118
|
|
|
51
119
|
def supports_sharding?
|
|
52
120
|
shard_names.any?
|
|
53
121
|
end
|
|
54
122
|
|
|
55
|
-
def
|
|
56
|
-
|
|
123
|
+
def on_replica?
|
|
124
|
+
current_shard_selection.on_replica?
|
|
57
125
|
end
|
|
58
126
|
|
|
59
|
-
def current_shard_selection
|
|
60
|
-
Thread.current[:shard_selection]
|
|
127
|
+
def current_shard_selection
|
|
128
|
+
Thread.current[:shard_selection] ||= ShardSelection.new
|
|
61
129
|
end
|
|
62
130
|
|
|
63
131
|
def current_shard_id
|
|
64
132
|
current_shard_selection.shard
|
|
65
133
|
end
|
|
66
134
|
|
|
67
|
-
def on_shard?
|
|
68
|
-
current_shard_selection.on_shard?
|
|
69
|
-
end
|
|
70
|
-
|
|
71
135
|
def shard_names
|
|
72
|
-
unless
|
|
73
|
-
raise "
|
|
74
|
-
end
|
|
75
|
-
unless config[SHARD_NAMES_CONFIG_KEY]
|
|
76
|
-
raise "No shards configured for #{shard_env}"
|
|
77
|
-
end
|
|
78
|
-
unless config[SHARD_NAMES_CONFIG_KEY].all? { |shard_name| shard_name.is_a?(Integer) }
|
|
79
|
-
raise "All shard names must be integers: #{config[SHARD_NAMES_CONFIG_KEY].inspect}."
|
|
136
|
+
unless config_for_env.fetch(SHARD_NAMES_CONFIG_KEY, []).all? { |shard_name| shard_name.is_a?(Integer) }
|
|
137
|
+
raise "All shard names must be integers: #{config_for_env[SHARD_NAMES_CONFIG_KEY].inspect}."
|
|
80
138
|
end
|
|
81
|
-
config[SHARD_NAMES_CONFIG_KEY]
|
|
82
|
-
end
|
|
83
139
|
|
|
84
|
-
|
|
85
|
-
with_default_shard { table_exists_without_default_shard? }
|
|
140
|
+
config_for_env[SHARD_NAMES_CONFIG_KEY] || []
|
|
86
141
|
end
|
|
87
142
|
|
|
88
143
|
private
|
|
89
144
|
|
|
90
|
-
def
|
|
91
|
-
|
|
145
|
+
def config_for_env
|
|
146
|
+
@_ars_config_for_env ||= {}
|
|
147
|
+
@_ars_config_for_env[shard_env] ||= begin
|
|
92
148
|
unless config = configurations[shard_env]
|
|
93
|
-
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.inspect})"
|
|
149
|
+
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.to_h.keys.inspect})"
|
|
94
150
|
end
|
|
95
|
-
|
|
96
|
-
|
|
151
|
+
|
|
152
|
+
config
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
alias_method :check_config_for_env, :config_for_env
|
|
156
|
+
|
|
157
|
+
def switch_connection(options)
|
|
158
|
+
if options.any?
|
|
159
|
+
if options.key?(:replica)
|
|
160
|
+
current_shard_selection.on_replica = options[:replica]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if options.key?(:shard)
|
|
164
|
+
check_config_for_env
|
|
165
|
+
|
|
166
|
+
current_shard_selection.shard = options[:shard]
|
|
97
167
|
end
|
|
98
|
-
self.current_shard_selection = selection
|
|
99
168
|
|
|
100
169
|
ensure_shard_connection
|
|
101
|
-
else
|
|
102
|
-
self.current_shard_selection = selection
|
|
103
170
|
end
|
|
104
171
|
end
|
|
105
172
|
|
|
106
173
|
def shard_env
|
|
107
|
-
ActiveRecordShards.
|
|
174
|
+
ActiveRecordShards.app_env
|
|
108
175
|
end
|
|
109
176
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
177
|
+
# Make these few schema related methods available before having switched to
|
|
178
|
+
# a shard.
|
|
179
|
+
def with_default_shard(&block)
|
|
180
|
+
if is_sharded? && current_shard_id.nil? && table_name != ActiveRecord::SchemaMigration.table_name
|
|
181
|
+
on_first_shard(&block)
|
|
113
182
|
else
|
|
114
183
|
yield
|
|
115
184
|
end
|
|
@@ -118,14 +187,30 @@ module ActiveRecordShards
|
|
|
118
187
|
def load_schema_with_default_shard!
|
|
119
188
|
with_default_shard { load_schema_without_default_shard! }
|
|
120
189
|
end
|
|
190
|
+
|
|
191
|
+
def table_exists_with_default_shard?
|
|
192
|
+
with_default_shard { table_exists_without_default_shard? }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
class PrimaryReplicaProxy
|
|
196
|
+
def initialize(target, which)
|
|
197
|
+
@target = target
|
|
198
|
+
@which = which
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
|
|
202
|
+
@target.on_primary_or_replica(@which) { @target.send(method, *args, &block) }
|
|
203
|
+
end
|
|
204
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
|
205
|
+
end
|
|
121
206
|
end
|
|
122
207
|
end
|
|
123
208
|
|
|
124
209
|
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
|
125
|
-
when '5.0'
|
|
126
|
-
require 'active_record_shards/connection_switcher_5_0'
|
|
127
210
|
when '5.1', '5.2'
|
|
128
|
-
require 'active_record_shards/
|
|
211
|
+
require 'active_record_shards/connection_switcher-5-1'
|
|
212
|
+
when '6.0'
|
|
213
|
+
require 'active_record_shards/connection_switcher-6-0'
|
|
129
214
|
else
|
|
130
215
|
raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
|
|
131
216
|
end
|