active_record_shards 3.17.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 +50 -115
- data/lib/active_record_shards.rb +2 -14
- data/lib/active_record_shards/association_collection_connection_selection.rb +1 -2
- data/lib/active_record_shards/configuration_parser.rb +1 -2
- 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 +37 -51
- data/lib/active_record_shards/default_slave_patches.rb +36 -44
- data/lib/active_record_shards/model.rb +5 -20
- data/lib/active_record_shards/shard_selection.rb +21 -58
- 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 +1 -5
- data/lib/active_record_shards/tasks.rb +19 -38
- metadata +52 -87
- 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/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
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
|
|
@@ -26,133 +17,77 @@ and make sure to require 'active\_record\_shards' in some way.
|
|
26
17
|
|
27
18
|
Add the slave and shard configuration to config/database.yml:
|
28
19
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
password:
|
38
|
-
slave:
|
39
|
-
host: db1_slave
|
40
|
-
shards:
|
41
|
-
1:
|
42
|
-
host: db_shard1
|
43
|
-
database: my_app_shard
|
44
|
-
slave:
|
45
|
-
host: db_shard1_slave
|
46
|
-
2:
|
47
|
-
host: db_shard2
|
48
|
-
database: my_app_shard
|
20
|
+
production:
|
21
|
+
adapter: mysql
|
22
|
+
encoding: utf8
|
23
|
+
database: my_app_main
|
24
|
+
pool: 5
|
25
|
+
host: db1
|
26
|
+
username: root
|
27
|
+
password:
|
49
28
|
slave:
|
50
|
-
host:
|
51
|
-
|
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
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
class Account < ActiveRecord::Base
|
106
|
-
not_sharded
|
47
|
+
All the models that live on the sharded database must inherit from ActiveRecordShards::ShardedModel:
|
107
48
|
|
108
|
-
|
109
|
-
|
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
|
-
|
123
|
-
end
|
60
|
+
class AccountMiddleware
|
61
|
+
def initialize(app)
|
62
|
+
@app = app
|
63
|
+
end
|
124
64
|
|
125
|
-
|
126
|
-
|
65
|
+
def call(env)
|
66
|
+
account = lookup_account(env)
|
127
67
|
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
131
75
|
end
|
132
|
-
else
|
133
|
-
@app.call(env)
|
134
|
-
end
|
135
|
-
end
|
136
76
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
end
|
141
|
-
```
|
77
|
+
def lookup_account(env)
|
78
|
+
...
|
79
|
+
end
|
80
|
+
end
|
142
81
|
|
143
82
|
You can switch to the slave databases at any point by wrapping your code in an on\_slave block:
|
144
83
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
end
|
149
|
-
```
|
84
|
+
ActiveRecord::Base.on_slave do
|
85
|
+
Account.find_by_big_expensive_query
|
86
|
+
end
|
150
87
|
|
151
88
|
This will perform the query on the slave, and mark the returned instances as read only. There is also a shortcut for this:
|
152
89
|
|
153
|
-
|
154
|
-
Account.on_slave.find_by_big_expensive_query
|
155
|
-
```
|
90
|
+
Account.on_slave.find_by_big_expensive_query
|
156
91
|
|
157
92
|
## Debugging
|
158
93
|
|
data/lib/active_record_shards.rb
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
require 'active_record'
|
4
3
|
require 'active_record/base'
|
5
4
|
require 'active_record_shards/configuration_parser'
|
6
5
|
require 'active_record_shards/model'
|
6
|
+
require 'active_record_shards/sharded_model'
|
7
7
|
require 'active_record_shards/shard_selection'
|
8
8
|
require 'active_record_shards/connection_switcher'
|
9
9
|
require 'active_record_shards/association_collection_connection_selection'
|
10
|
-
require 'active_record_shards/migration'
|
11
10
|
require 'active_record_shards/default_slave_patches'
|
12
|
-
require 'active_record_shards/schema_dumper_extension'
|
13
11
|
|
14
12
|
module ActiveRecordShards
|
15
13
|
def self.rails_env
|
@@ -26,14 +24,4 @@ ActiveRecord::Base.extend(ActiveRecordShards::ConnectionSwitcher)
|
|
26
24
|
ActiveRecord::Base.extend(ActiveRecordShards::DefaultSlavePatches)
|
27
25
|
ActiveRecord::Relation.include(ActiveRecordShards::DefaultSlavePatches::ActiveRelationPatches)
|
28
26
|
ActiveRecord::Associations::CollectionProxy.include(ActiveRecordShards::AssociationCollectionConnectionSelection)
|
29
|
-
ActiveRecord::Associations::Builder::HasAndBelongsToMany.include(ActiveRecordShards::DefaultSlavePatches::
|
30
|
-
ActiveRecord::SchemaDumper.prepend(ActiveRecordShards::SchemaDumperExtension)
|
31
|
-
|
32
|
-
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
33
|
-
when '4.2'
|
34
|
-
require 'active_record_shards/patches-4-2'
|
35
|
-
when '5.0', '5.1', '5.2', '6.0'
|
36
|
-
:ok
|
37
|
-
else
|
38
|
-
raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
|
39
|
-
end
|
27
|
+
ActiveRecord::Associations::Builder::HasAndBelongsToMany.include(ActiveRecordShards::DefaultSlavePatches::HasAndBelongsToManyBuilderExtension)
|
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
module ActiveRecordShards
|
4
3
|
module AssociationCollectionConnectionSelection
|
5
4
|
def on_slave_if(condition)
|
@@ -32,7 +31,7 @@ module ActiveRecordShards
|
|
32
31
|
@which = which
|
33
32
|
end
|
34
33
|
|
35
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/
|
34
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
|
36
35
|
reflection = @association_collection.proxy_association.reflection
|
37
36
|
reflection.klass.on_cx_switch_block(@which) { @association_collection.send(method, *args, &block) }
|
38
37
|
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')
|
@@ -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.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.keys.inspect})"
|
8
|
-
end
|
9
|
-
|
10
|
-
name
|
11
|
-
end
|
12
|
-
|
13
3
|
private
|
14
4
|
|
15
5
|
def ensure_shard_connection
|
@@ -1,33 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
require 'active_record_shards/shard_support'
|
4
3
|
|
5
4
|
module ActiveRecordShards
|
6
5
|
module ConnectionSwitcher
|
7
|
-
SHARD_NAMES_CONFIG_KEY = 'shard_names'
|
6
|
+
SHARD_NAMES_CONFIG_KEY = 'shard_names'.freeze
|
8
7
|
|
9
8
|
def self.extended(base)
|
10
|
-
|
11
|
-
|
12
|
-
base.singleton_class.send(:alias_method, :load_schema!, :load_schema_with_default_shard!)
|
13
|
-
else
|
14
|
-
base.singleton_class.send(:alias_method, :columns_without_default_shard, :columns)
|
15
|
-
base.singleton_class.send(:alias_method, :columns, :columns_with_default_shard)
|
16
|
-
end
|
9
|
+
base.singleton_class.send(:alias_method, :load_schema_without_default_shard!, :load_schema!)
|
10
|
+
base.singleton_class.send(:alias_method, :load_schema!, :load_schema_with_default_shard!)
|
17
11
|
|
18
12
|
base.singleton_class.send(:alias_method, :table_exists_without_default_shard?, :table_exists?)
|
19
13
|
base.singleton_class.send(:alias_method, :table_exists?, :table_exists_with_default_shard?)
|
20
14
|
end
|
21
15
|
|
22
|
-
def default_shard=(new_default_shard)
|
23
|
-
ActiveRecordShards::ShardSelection.default_shard = new_default_shard
|
24
|
-
switch_connection(shard: new_default_shard)
|
25
|
-
end
|
26
|
-
|
27
|
-
def on_primary_db(&block)
|
28
|
-
on_shard(nil, &block)
|
29
|
-
end
|
30
|
-
|
31
16
|
def on_shard(shard)
|
32
17
|
old_options = current_shard_selection.options
|
33
18
|
switch_connection(shard: shard) if supports_sharding?
|
@@ -36,9 +21,9 @@ module ActiveRecordShards
|
|
36
21
|
switch_connection(old_options)
|
37
22
|
end
|
38
23
|
|
39
|
-
def on_first_shard
|
24
|
+
def on_first_shard
|
40
25
|
shard_name = shard_names.first
|
41
|
-
on_shard(shard_name
|
26
|
+
on_shard(shard_name) { yield }
|
42
27
|
end
|
43
28
|
|
44
29
|
def shards
|
@@ -100,11 +85,6 @@ module ActiveRecordShards
|
|
100
85
|
on_master_or_slave(:master, &block)
|
101
86
|
end
|
102
87
|
|
103
|
-
# just to ease the transition from replica to active_record_shards
|
104
|
-
alias_method :with_slave, :on_slave
|
105
|
-
alias_method :with_slave_if, :on_slave_if
|
106
|
-
alias_method :with_slave_unless, :on_slave_unless
|
107
|
-
|
108
88
|
def on_cx_switch_block(which, force: false, construct_ro_scope: nil, &block)
|
109
89
|
@disallow_slave ||= 0
|
110
90
|
@disallow_slave += 1 if which == :master
|
@@ -126,6 +106,16 @@ module ActiveRecordShards
|
|
126
106
|
switch_connection(old_options) if old_options
|
127
107
|
end
|
128
108
|
|
109
|
+
def connection_specification_name
|
110
|
+
name = current_shard_selection.resolve_connection_name(sharded: is_sharded?, configurations: configurations)
|
111
|
+
|
112
|
+
unless configurations[name] || name == "primary"
|
113
|
+
raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.inspect})"
|
114
|
+
end
|
115
|
+
|
116
|
+
name
|
117
|
+
end
|
118
|
+
|
129
119
|
def supports_sharding?
|
130
120
|
shard_names.any?
|
131
121
|
end
|
@@ -135,7 +125,7 @@ module ActiveRecordShards
|
|
135
125
|
end
|
136
126
|
|
137
127
|
def current_shard_selection
|
138
|
-
Thread.current[:shard_selection] ||= ShardSelection.new
|
128
|
+
Thread.current[:shard_selection] ||= ShardSelection.new(shard_names.first)
|
139
129
|
end
|
140
130
|
|
141
131
|
def current_shard_id
|
@@ -144,31 +134,35 @@ module ActiveRecordShards
|
|
144
134
|
|
145
135
|
def shard_names
|
146
136
|
unless config = configurations[shard_env]
|
147
|
-
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.
|
137
|
+
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.inspect})"
|
138
|
+
end
|
139
|
+
unless config[SHARD_NAMES_CONFIG_KEY]
|
140
|
+
raise "No shards configured for #{shard_env}"
|
148
141
|
end
|
149
|
-
unless config
|
142
|
+
unless config[SHARD_NAMES_CONFIG_KEY].all? { |shard_name| shard_name.is_a?(Integer) }
|
150
143
|
raise "All shard names must be integers: #{config[SHARD_NAMES_CONFIG_KEY].inspect}."
|
151
144
|
end
|
152
|
-
|
153
|
-
config[SHARD_NAMES_CONFIG_KEY] || []
|
145
|
+
config[SHARD_NAMES_CONFIG_KEY]
|
154
146
|
end
|
155
147
|
|
156
148
|
private
|
157
149
|
|
158
150
|
def switch_connection(options)
|
159
151
|
if options.any?
|
160
|
-
if options.key?(:slave)
|
161
|
-
current_shard_selection.on_slave = options[:slave]
|
162
|
-
end
|
163
|
-
|
164
152
|
if options.key?(:shard)
|
165
|
-
unless configurations[shard_env]
|
166
|
-
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.
|
153
|
+
unless config = configurations[shard_env]
|
154
|
+
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.inspect})"
|
155
|
+
end
|
156
|
+
unless config['shard_names'].include?(options[:shard])
|
157
|
+
raise "Did not find shard #{options[:shard]} in configurations"
|
167
158
|
end
|
168
|
-
|
169
159
|
current_shard_selection.shard = options[:shard]
|
170
160
|
end
|
171
161
|
|
162
|
+
if options.key?(:slave)
|
163
|
+
current_shard_selection.on_slave = options[:slave]
|
164
|
+
end
|
165
|
+
|
172
166
|
ensure_shard_connection
|
173
167
|
end
|
174
168
|
end
|
@@ -177,22 +171,16 @@ module ActiveRecordShards
|
|
177
171
|
ActiveRecordShards.rails_env
|
178
172
|
end
|
179
173
|
|
180
|
-
def with_default_shard
|
174
|
+
def with_default_shard
|
181
175
|
if is_sharded? && current_shard_id.nil? && table_name != ActiveRecord::SchemaMigration.table_name
|
182
|
-
on_first_shard
|
176
|
+
on_first_shard { yield }
|
183
177
|
else
|
184
178
|
yield
|
185
179
|
end
|
186
180
|
end
|
187
181
|
|
188
|
-
|
189
|
-
|
190
|
-
with_default_shard { load_schema_without_default_shard! }
|
191
|
-
end
|
192
|
-
else
|
193
|
-
def columns_with_default_shard
|
194
|
-
with_default_shard { columns_without_default_shard }
|
195
|
-
end
|
182
|
+
def load_schema_with_default_shard!
|
183
|
+
with_default_shard { load_schema_without_default_shard! }
|
196
184
|
end
|
197
185
|
|
198
186
|
def table_exists_with_default_shard?
|
@@ -205,7 +193,7 @@ module ActiveRecordShards
|
|
205
193
|
@which = which
|
206
194
|
end
|
207
195
|
|
208
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/
|
196
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
|
209
197
|
@target.on_master_or_slave(@which) { @target.send(method, *args, &block) }
|
210
198
|
end
|
211
199
|
end
|
@@ -213,11 +201,9 @@ module ActiveRecordShards
|
|
213
201
|
end
|
214
202
|
|
215
203
|
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
216
|
-
when '4.2'
|
217
|
-
require 'active_record_shards/connection_switcher-4-2'
|
218
204
|
when '5.0'
|
219
205
|
require 'active_record_shards/connection_switcher-5-0'
|
220
|
-
when '5.1'
|
206
|
+
when '5.1'
|
221
207
|
require 'active_record_shards/connection_switcher-5-1'
|
222
208
|
else
|
223
209
|
raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
|