active_record_shards 3.17.0 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: cb66e1dcadbbd2c1c6f619cb1504ad0b55cfba8c4e36c76015d74781f7e29b5b
4
- data.tar.gz: 0f63d2ffbdae6ded142d886af712a10c6b2f0fc59cdc8ab6de9c59d078663d7c
2
+ SHA1:
3
+ metadata.gz: 88819bbec3c2e39bc44c5f6172aa5bf9a3a7e097
4
+ data.tar.gz: bc407bce7e0f3ae6bc7bcf9041ac49cd6c8077a8
5
5
  SHA512:
6
- metadata.gz: 8cc61cd8b0c9b18a41c3b7ab1cd21d16a32d03b51e372855792bec89fe40bd45a4900b2ccfb9fdbafcdc841a98301e9ced9c91d1ba44100d12660723ee655682
7
- data.tar.gz: 48e7fdb5d1501834e9008efd22ec617af5a429aab37bece0f435547f49b5bec539950bbf08c5412b336a201f4d756c62f45be580d063b250ce8e10ee89aaf423
6
+ metadata.gz: e5f4fb85b1321bdb55edf8118ea944fb7fd1188a56350c87ebf2a560e5ad63e145da065eb9859532bc4bdb545b89ac22d8d65ec2810028fa995e84c88030ab6f
7
+ data.tar.gz: b8bc4ac010819230082d490c2e2850b80ce5ea5018d1f5323536894e58005d17cfb018b6757a339444864d71e76484c22d9517ca197211bf90db1229306e6777
data/README.md CHANGED
@@ -1,20 +1,11 @@
1
- [![CircleCI build status](https://circleci.com/gh/zendesk/active_record_shards/tree/master.svg?style=svg)](https://circleci.com/gh/zendesk/active_record_shards/tree/master)
1
+ [![Build Status](https://secure.travis-ci.org/zendesk/active_record_shards.png)](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 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
-
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
- ```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
- 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: db_shard2_slave
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 shared database must be marked as not\_sharded:
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
- has_many :projects
109
- end
49
+ class Account < ActiveRecord::Base
50
+ has_many :projects
51
+ end
110
52
 
111
- class Project < ActiveRecord::Base
112
- belongs_to :account
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
- ```ruby
120
- class AccountMiddleware
121
- def initialize(app)
122
- @app = app
123
- end
60
+ class AccountMiddleware
61
+ def initialize(app)
62
+ @app = app
63
+ end
124
64
 
125
- def call(env)
126
- account = lookup_account(env)
65
+ def call(env)
66
+ account = lookup_account(env)
127
67
 
128
- if account
129
- ActiveRecord::Base.on_shard(account.shard_id) do
130
- @app.call(env)
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
- def lookup_account(env)
138
- # ...
139
- end
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
- ```ruby
146
- ActiveRecord::Base.on_slave do
147
- Account.find_by_big_expensive_query
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
- ```ruby
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
 
@@ -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::Rails41HasAndBelongsToManyBuilderExtension)
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/MethodMissingSuper, Style/MissingRespondToMissing
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.to_h.deep_dup
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
- if ActiveRecord::VERSION::MAJOR >= 5
11
- base.singleton_class.send(:alias_method, :load_schema_without_default_shard!, :load_schema!)
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(&block)
24
+ def on_first_shard
40
25
  shard_name = shard_names.first
41
- on_shard(shard_name, &block)
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.keys.inspect})"
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.fetch(SHARD_NAMES_CONFIG_KEY, []).all? { |shard_name| shard_name.is_a?(Integer) }
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.keys.inspect})"
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(&block)
174
+ def with_default_shard
181
175
  if is_sharded? && current_shard_id.nil? && table_name != ActiveRecord::SchemaMigration.table_name
182
- on_first_shard(&block)
176
+ on_first_shard { yield }
183
177
  else
184
178
  yield
185
179
  end
186
180
  end
187
181
 
188
- if ActiveRecord::VERSION::MAJOR >= 5
189
- def load_schema_with_default_shard!
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/MethodMissingSuper, Style/MissingRespondToMissing
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', '5.2', '6.0'
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}"