active_record_shards 3.22.0 → 4.0.0.beta1
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 +5 -5
- data/README.md +57 -126
- data/lib/active_record_shards/association_collection_connection_selection.rb +14 -23
- data/lib/active_record_shards/configuration_parser.rb +6 -30
- data/lib/active_record_shards/connection_switcher-5-0.rb +0 -10
- data/lib/active_record_shards/connection_switcher-5-1.rb +0 -10
- data/lib/active_record_shards/connection_switcher.rb +71 -115
- data/lib/active_record_shards/default_slave_patches.rb +134 -3
- data/lib/active_record_shards/model.rb +16 -35
- data/lib/active_record_shards/shard_selection.rb +27 -66
- data/lib/active_record_shards/shard_support.rb +0 -1
- data/lib/active_record_shards/sharded_model.rb +15 -0
- data/lib/active_record_shards/sql_comments.rb +4 -11
- data/lib/active_record_shards/tasks.rb +22 -41
- data/lib/active_record_shards.rb +6 -126
- metadata +64 -74
- data/lib/active_record_shards/connection_handler.rb +0 -8
- data/lib/active_record_shards/connection_pool.rb +0 -36
- data/lib/active_record_shards/connection_specification.rb +0 -19
- data/lib/active_record_shards/connection_switcher-4-2.rb +0 -56
- data/lib/active_record_shards/connection_switcher-6-0.rb +0 -30
- data/lib/active_record_shards/default_replica_patches.rb +0 -278
- data/lib/active_record_shards/deprecation.rb +0 -12
- data/lib/active_record_shards/migration.rb +0 -125
- data/lib/active_record_shards/patches-4-2.rb +0 -9
- data/lib/active_record_shards/schema_dumper_extension.rb +0 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 88819bbec3c2e39bc44c5f6172aa5bf9a3a7e097
|
|
4
|
+
data.tar.gz: bc407bce7e0f3ae6bc7bcf9041ac49cd6c8077a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5f4fb85b1321bdb55edf8118ea944fb7fd1188a56350c87ebf2a560e5ad63e145da065eb9859532bc4bdb545b89ac22d8d65ec2810028fa995e84c88030ab6f
|
|
7
|
+
data.tar.gz: b8bc4ac010819230082d490c2e2850b80ce5ea5018d1f5323536894e58005d17cfb018b6757a339444864d71e76484c22d9517ca197211bf90db1229306e6777
|
data/README.md
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
|
-
[](http://travis-ci.org/zendesk/active_record_shards)
|
|
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 slaves. 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
|
|
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)
|
|
8
|
+
ActiveRecord Shards has been used and tested on Rails 5.0 and has in some form or another been used in production on a large Rails app for several years.
|
|
18
9
|
|
|
19
10
|
## Installation
|
|
20
11
|
|
|
@@ -24,143 +15,83 @@ and make sure to require 'active\_record\_shards' in some way.
|
|
|
24
15
|
|
|
25
16
|
## Configuration
|
|
26
17
|
|
|
27
|
-
Add the
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
host: db_shard2_replica
|
|
51
|
-
```
|
|
18
|
+
Add the slave and shard configuration to config/database.yml:
|
|
19
|
+
|
|
20
|
+
production:
|
|
21
|
+
adapter: mysql
|
|
22
|
+
encoding: utf8
|
|
23
|
+
database: my_app_main
|
|
24
|
+
pool: 5
|
|
25
|
+
host: db1
|
|
26
|
+
username: root
|
|
27
|
+
password:
|
|
28
|
+
slave:
|
|
29
|
+
host: db1_slave
|
|
30
|
+
shards:
|
|
31
|
+
1:
|
|
32
|
+
host: db_shard1
|
|
33
|
+
database: my_app_shard
|
|
34
|
+
slave:
|
|
35
|
+
host: db_shard1_slave
|
|
36
|
+
2:
|
|
37
|
+
host: db_shard2
|
|
38
|
+
database: my_app_shard
|
|
39
|
+
slave:
|
|
40
|
+
host: db_shard2_slave
|
|
52
41
|
|
|
53
42
|
basically connections inherit configuration from the parent configuration file.
|
|
54
43
|
|
|
55
|
-
## Migrations
|
|
56
|
-
|
|
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
|
|
68
|
-
|
|
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
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
###### Create a table for the sharded model
|
|
85
|
-
|
|
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
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
```
|
|
98
|
-
|
|
99
44
|
## Usage
|
|
100
45
|
|
|
101
46
|
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
|
|
47
|
+
All the models that live on the sharded database must inherit from ActiveRecordShards::ShardedModel:
|
|
103
48
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
has_many :projects
|
|
109
|
-
end
|
|
49
|
+
class Account < ActiveRecord::Base
|
|
50
|
+
has_many :projects
|
|
51
|
+
end
|
|
110
52
|
|
|
111
|
-
class Project <
|
|
112
|
-
|
|
113
|
-
end
|
|
114
|
-
```
|
|
53
|
+
class Project < ActiveRecordShards::ShardedModel
|
|
54
|
+
belongs_to :account
|
|
55
|
+
end
|
|
115
56
|
|
|
116
57
|
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
|
|
117
58
|
in a rack middleware and switch to the right shard:
|
|
118
59
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
@app = app
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def call(env)
|
|
126
|
-
account = lookup_account(env)
|
|
127
|
-
|
|
128
|
-
if account
|
|
129
|
-
ActiveRecord::Base.on_shard(account.shard_id) do
|
|
130
|
-
@app.call(env)
|
|
60
|
+
class AccountMiddleware
|
|
61
|
+
def initialize(app)
|
|
62
|
+
@app = app
|
|
131
63
|
end
|
|
132
|
-
else
|
|
133
|
-
@app.call(env)
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
64
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
```
|
|
65
|
+
def call(env)
|
|
66
|
+
account = lookup_account(env)
|
|
142
67
|
|
|
143
|
-
|
|
68
|
+
if account
|
|
69
|
+
ActiveRecord::Base.on_shard(account.shard_id) do
|
|
70
|
+
@app.call(env)
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
@app.call(env)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
144
76
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
end
|
|
149
|
-
```
|
|
77
|
+
def lookup_account(env)
|
|
78
|
+
...
|
|
79
|
+
end
|
|
80
|
+
end
|
|
150
81
|
|
|
151
|
-
|
|
82
|
+
You can switch to the slave databases at any point by wrapping your code in an on\_slave block:
|
|
152
83
|
|
|
153
|
-
|
|
154
|
-
Account.
|
|
155
|
-
|
|
84
|
+
ActiveRecord::Base.on_slave do
|
|
85
|
+
Account.find_by_big_expensive_query
|
|
86
|
+
end
|
|
156
87
|
|
|
157
|
-
|
|
88
|
+
This will perform the query on the slave, and mark the returned instances as read only. There is also a shortcut for this:
|
|
158
89
|
|
|
159
|
-
|
|
90
|
+
Account.on_slave.find_by_big_expensive_query
|
|
160
91
|
|
|
161
92
|
## Debugging
|
|
162
93
|
|
|
163
|
-
Show if a query went to
|
|
94
|
+
Show if a query went to master or slave in the logs:
|
|
164
95
|
|
|
165
96
|
```Ruby
|
|
166
97
|
require 'active_record_shards/sql_comments'
|
|
@@ -1,49 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
module ActiveRecordShards
|
|
4
3
|
module AssociationCollectionConnectionSelection
|
|
5
|
-
def
|
|
6
|
-
condition ?
|
|
4
|
+
def on_slave_if(condition)
|
|
5
|
+
condition ? on_slave : self
|
|
7
6
|
end
|
|
8
|
-
alias_method :on_slave_if, :on_replica_if
|
|
9
7
|
|
|
10
|
-
def
|
|
11
|
-
|
|
8
|
+
def on_slave_unless(condition)
|
|
9
|
+
on_slave_if(!condition)
|
|
12
10
|
end
|
|
13
|
-
alias_method :on_slave_unless, :on_replica_unless
|
|
14
11
|
|
|
15
|
-
def
|
|
16
|
-
condition ?
|
|
12
|
+
def on_master_if(condition)
|
|
13
|
+
condition ? on_master : self
|
|
17
14
|
end
|
|
18
|
-
alias_method :on_master_if, :on_primary_if
|
|
19
15
|
|
|
20
|
-
def
|
|
21
|
-
|
|
16
|
+
def on_master_unless(condition)
|
|
17
|
+
on_master_if(!condition)
|
|
22
18
|
end
|
|
23
|
-
alias_method :on_master_unless, :on_primary_unless
|
|
24
19
|
|
|
25
|
-
def
|
|
26
|
-
|
|
20
|
+
def on_slave
|
|
21
|
+
MasterSlaveProxy.new(self, :slave)
|
|
27
22
|
end
|
|
28
|
-
alias_method :on_slave, :on_replica
|
|
29
23
|
|
|
30
|
-
def
|
|
31
|
-
|
|
24
|
+
def on_master
|
|
25
|
+
MasterSlaveProxy.new(self, :master)
|
|
32
26
|
end
|
|
33
|
-
alias_method :on_master, :on_primary
|
|
34
27
|
|
|
35
|
-
class
|
|
28
|
+
class MasterSlaveProxy
|
|
36
29
|
def initialize(association_collection, which)
|
|
37
30
|
@association_collection = association_collection
|
|
38
31
|
@which = which
|
|
39
32
|
end
|
|
40
33
|
|
|
41
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/
|
|
34
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
|
|
42
35
|
reflection = @association_collection.proxy_association.reflection
|
|
43
36
|
reflection.klass.on_cx_switch_block(@which) { @association_collection.send(method, *args, &block) }
|
|
44
37
|
end
|
|
45
38
|
end
|
|
46
|
-
|
|
47
|
-
MasterSlaveProxy = PrimaryReplicaProxy
|
|
48
39
|
end
|
|
49
40
|
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require 'active_support/core_ext'
|
|
4
3
|
|
|
5
4
|
module ActiveRecordShards
|
|
@@ -7,7 +6,7 @@ module ActiveRecordShards
|
|
|
7
6
|
module_function
|
|
8
7
|
|
|
9
8
|
def explode(conf)
|
|
10
|
-
conf = conf.
|
|
9
|
+
conf = conf.deep_dup
|
|
11
10
|
|
|
12
11
|
conf.to_a.each do |env_name, env_config|
|
|
13
12
|
next unless shards = env_config.delete('shards')
|
|
@@ -24,31 +23,10 @@ module ActiveRecordShards
|
|
|
24
23
|
end
|
|
25
24
|
|
|
26
25
|
conf.to_a.each do |env_name, env_config|
|
|
27
|
-
if
|
|
28
|
-
expand_child!(env_config,
|
|
29
|
-
conf["#{env_name}
|
|
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
|
|
26
|
+
if slave_conf = env_config.delete('slave')
|
|
27
|
+
expand_child!(env_config, slave_conf)
|
|
28
|
+
conf["#{env_name}_slave"] = slave_conf
|
|
37
29
|
end
|
|
38
|
-
# rubocop:enable Style/Next
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
conf
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def replace_slave_keys(conf)
|
|
45
|
-
conf.to_a.each do |env_name, env_config|
|
|
46
|
-
next unless env_name.end_with?("_slave")
|
|
47
|
-
|
|
48
|
-
replica_key = env_name.sub(/_slave$/, "_replica")
|
|
49
|
-
next if conf.key?(replica_key)
|
|
50
|
-
|
|
51
|
-
conf[replica_key] = env_config.deep_dup
|
|
52
30
|
end
|
|
53
31
|
|
|
54
32
|
conf
|
|
@@ -56,16 +34,14 @@ module ActiveRecordShards
|
|
|
56
34
|
|
|
57
35
|
def expand_child!(parent, child)
|
|
58
36
|
parent.each do |key, value|
|
|
59
|
-
unless ['slave', '
|
|
37
|
+
unless ['slave', 'shards'].include?(key) || value.is_a?(Hash)
|
|
60
38
|
child[key] ||= value
|
|
61
39
|
end
|
|
62
40
|
end
|
|
63
41
|
end
|
|
64
42
|
|
|
65
43
|
def configurations_with_shard_explosion=(conf)
|
|
66
|
-
|
|
67
|
-
configuration_with_slave_keys_replaced = replace_slave_keys(exploded_configuration)
|
|
68
|
-
self.configurations_without_shard_explosion = configuration_with_slave_keys_replaced
|
|
44
|
+
self.configurations_without_shard_explosion = explode(conf)
|
|
69
45
|
end
|
|
70
46
|
|
|
71
47
|
def self.extended(base)
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
module ActiveRecordShards
|
|
2
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
|
-
|
|
13
3
|
private
|
|
14
4
|
|
|
15
5
|
def ensure_shard_connection
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
module ActiveRecordShards
|
|
2
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
|
-
|
|
13
3
|
private
|
|
14
4
|
|
|
15
5
|
def ensure_shard_connection
|