mixed_gauge 1.0.0 → 1.1.0

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
2
  SHA1:
3
- metadata.gz: b52a83a9e2dcfa88c05591683f5f3f8ca6cdc25e
4
- data.tar.gz: 2ebdd85105cf34b3d4cf09485293953e85894be0
3
+ metadata.gz: db4722c025b0f789f356bc263a7b5abd0e4824e9
4
+ data.tar.gz: 2b09c8ec6892123fdde93ccd227bf4e21cf65500
5
5
  SHA512:
6
- metadata.gz: 85c09e39e2a6963dda8afab08b8bad16e7ec4be4ac506f8b208295d84842f9d4b92344e099072cfdf3443af2ce7a7e51d7f629d0940aadeb42ff691541d43948
7
- data.tar.gz: 8c014307fa8eae21d653ebed9264f01ba8408db1364a158fcec682fdab9a618f81a9aeff5dbc22e25404228992ed191485e96e26510a33ad9c35d5081a9cd9b9
6
+ metadata.gz: 6f6b47b28eff87fa760c363acc7943200d2ff9fc12fce05aaf245acaa5a8890053b8cc8a698e4a9153740077bcbd8fcb01c8001f8d94b6e2fda80858c3461eef
7
+ data.tar.gz: a9c5e57c778f0be58568e237edfcea43db2e65b1452cbb5e4bcecb54e64bb61b59feb4052b8a5c09fef00f432213cb7cb4814c5f280ae2f75e7a471cb3e53e78
@@ -1,6 +1,5 @@
1
1
  language: ruby
2
2
  sudo: false
3
- script: "bundle exec rake && bundle exec ruby spec/performance_test.rb"
4
3
  branches:
5
4
  only:
6
5
  - master
@@ -1,4 +1,7 @@
1
1
  # CHANGELOG for mixed_gauge
2
+ ## 1.1.0
3
+ - Replication support. #5
4
+
2
5
  ## 1.0.0
3
6
  - Change cluster slots definition interface: does not specify entire slots,
4
7
  but specify only slots size.
data/README.md CHANGED
@@ -1,11 +1,12 @@
1
- # MixedGauge
1
+ # mixed_gauge
2
2
  [![Build Status](https://travis-ci.org/taiki45/mixed_gauge.svg?branch=master)](https://travis-ci.org/taiki45/mixed_gauge) [![Coverage Status](https://coveralls.io/repos/taiki45/mixed_gauge/badge.svg?branch=master)](https://coveralls.io/r/taiki45/mixed_gauge?branch=master) [![Code Climate](https://codeclimate.com/github/taiki45/mixed_gauge/badges/gpa.svg)](https://codeclimate.com/github/taiki45/mixed_gauge) [![Gem Version](https://badge.fury.io/rb/mixed_gauge.svg)](http://badge.fury.io/rb/mixed_gauge)
3
3
 
4
4
  A simple and robust ActiveRecord extension for database sharding.
5
5
  mixed_gauge offers shards management with hash slots and re-sharding support.
6
6
  It enable you to execute efficient queries to single node with KVS-like interface.
7
- And you can even execute limited RDB queries to all nodes with ActiveRecord interface,
8
- 'course in-parallel.
7
+ And you can even execute limited RDB queries to all nodes with ActiveRecord interface in-parallel.
8
+
9
+ mixed_gauge is already used in production. [(blog post in Japanese)](http://techlife.cookpad.com/entry/2015/06/22/134108)
9
10
 
10
11
  ## Goal and concept
11
12
  - Simple
@@ -14,7 +15,7 @@ And you can even execute limited RDB queries to all nodes with ActiveRecord inte
14
15
 
15
16
  Database sharding tend to be over-complexed. There are cases which need these complex database sharding but in some cases database sharding can be more simple. The large data set which is enoght big to partition should be designed to be distributed, or should be re-design if it wasn't. Design to be distributed uses key based relation or reverse indexes to fits its limitation. In that case, the data set is almost design to be distributed, mixed_gauge strongly encourages your database sharding by its simplicity.
16
17
 
17
- We, offer 24/7 services, must keep our services running. mixed_gauge supports online migrations: adding new nodes to cluster or removing some existing nodes from cluster. It comes with "key distibution model with hash slots" and database replication and multi-master replication. In sharding we need re-sharding, move data from node to anoter node in cluster, when adding or removing new nodes from cluster. But by setting some rule to node management and using replication, we can finish moving data before adding or removing nodes. The detail operations are specified later chapter of this document.
18
+ We, offer 24/7 services, must keep our services running. mixed_gauge supports online migrations: adding new nodes to cluster or removing some existing nodes from cluster. It comes with "key distibution model with hash slots" and database replication and multi-master replication. In sharding we need re-sharding, move data from node to another node in cluster, when adding or removing new nodes from cluster. But by setting some rule to node management and using replication, we can finish moving data before adding or removing nodes. The detail operations are specified later chapter of this document.
18
19
 
19
20
  All operaions should be rollback-able in case of any failures. mixed_gauge's node management can rollback adding and removing nodes operation. The detail operations are specified later chapter of this document.
20
21
 
@@ -240,6 +241,107 @@ access_token = AccessToken.put!
240
241
  access_token.token #=> a generated token
241
242
  ```
242
243
 
244
+ ## Sharding with Replication
245
+ mixed_gauge also supports replication.
246
+
247
+ In case you have 2 shards in cluster and each shard have read replica.
248
+
249
+ - db-user-101 --replicated--> db-user-102
250
+ - db-user-201 --replicated--> db-user-202
251
+
252
+ Your database connection configuration might be like this:
253
+
254
+ ```yaml
255
+ # database.yml
256
+ production_user_001:
257
+ adapter: mysql2
258
+ username: user_writable
259
+ host: db-user-101
260
+ production_user_002:
261
+ adapter: mysql2
262
+ username: user_writable
263
+ host: db-user-201
264
+ production_user_readonly_001:
265
+ adapter: mysql2
266
+ username: user_readonly
267
+ host: db-user-102
268
+ production_user_readonly_002:
269
+ adapter: mysql2
270
+ username: user_writable
271
+ host: db-user-202
272
+ ```
273
+
274
+ Your initializer for mixed_gauge might be like this:
275
+
276
+ ```ruby
277
+ MixedGauge.configure do |config|
278
+ config.define_cluster(:user) do |cluster|
279
+ cluster.define_slot_size(1048576)
280
+ cluster.register(0..524287, :production_user_001)
281
+ cluster.register(524288..1048575, :production_user_002)
282
+ end
283
+
284
+ config.define_cluster(:user_readonly) do |cluster|
285
+ cluster.define_slot_size(1048576)
286
+ cluster.register(0..524287, :production_user_readonly_001)
287
+ cluster.register(524288..1048575, :production_user_readonly_002)
288
+ end
289
+ end
290
+ ```
291
+
292
+ You can split read/write by defining AR model class for each connection:
293
+
294
+ ```ruby
295
+ class User < ActiveRecord::Base
296
+ include MixedGauge::Model
297
+ use_cluster :user
298
+ def_distkey :email
299
+ end
300
+
301
+ class UserReadonly < ActiveRecord::Base
302
+ self.table_name = 'users'
303
+
304
+ include MixedGauge::Model
305
+ use_cluster :user_readonly
306
+ def_distkey :email
307
+ end
308
+
309
+ User.put!(name: 'Alice', email: 'alice@example.com')
310
+ UserReadonly.get('alice@example.com')
311
+ ```
312
+
313
+ If you want to switch specific shard to another shard in another cluster, define mapping between each model:
314
+
315
+ ```ruby
316
+ class User < ActiveRecord::Base
317
+ include MixedGauge::Model
318
+ use_cluster :user
319
+ def_distkey :email
320
+
321
+ replicates_with slave: :UserReadonly
322
+ end
323
+
324
+ class UserReadonly < ActiveRecord::Base
325
+ self.table_name = 'users'
326
+
327
+ include MixedGauge::Model
328
+ use_cluster :user_readonly
329
+ def_distkey :email
330
+
331
+ replicates_with master: :User
332
+ end
333
+ ```
334
+
335
+ You can switch to another model which have connection to the shard by calling `.switch`:
336
+
337
+ ```ruby
338
+ UserReadonly.all_shards do |readonly|
339
+ target_ids = readonly.where(age: 0).pluck(:id)
340
+ readonly.switch(:master) do |writable|
341
+ writable.where(id: target_ids).delete_all
342
+ end
343
+ end
344
+ ```
243
345
 
244
346
  ## Advanced configuration
245
347
  ### Hash fucntion
data/Rakefile CHANGED
@@ -6,6 +6,11 @@ require 'bundler/gem_tasks'
6
6
  begin
7
7
  require 'rspec/core/rake_task'
8
8
  RSpec::Core::RakeTask.new(:spec)
9
- task :default => :spec
10
9
  rescue LoadError
11
10
  end
11
+
12
+ task :performance_test do
13
+ ruby 'spec/performance_test.rb'
14
+ end
15
+
16
+ task :default => [:spec, :performance_test]
@@ -7,6 +7,7 @@ require 'mixed_gauge/cluster_config'
7
7
  require 'mixed_gauge/config'
8
8
  require 'mixed_gauge/routing'
9
9
  require 'mixed_gauge/shard_repository'
10
+ require 'mixed_gauge/replication_mapping'
10
11
  require 'mixed_gauge/all_shards_in_parallel'
11
12
  require 'mixed_gauge/model'
12
13
 
@@ -1,7 +1,7 @@
1
1
  module MixedGauge
2
2
  # Mapping of slot -> connection_name.
3
3
  class ClusterConfig
4
- attr_reader :name
4
+ attr_reader :name, :connection_registry
5
5
 
6
6
  # @param [Symbol] name
7
7
  def initialize(name)
@@ -6,6 +6,7 @@ module MixedGauge
6
6
  # include MixedGauge::Model
7
7
  # use_cluster :user
8
8
  # def_distkey :email
9
+ # replicates_with slave: :UserReadonly, backgroud: :UserBackground
9
10
  # end
10
11
  #
11
12
  # User.put!(email: 'alice@example.com', name: 'alice')
@@ -22,11 +23,12 @@ module MixedGauge
22
23
  class_attribute :cluster_routing, instance_writer: false
23
24
  class_attribute :shard_repository, instance_writer: false
24
25
  class_attribute :distkey, instance_writer: false
26
+ class_attribute :replication_mapping, instance_writer: false
25
27
  end
26
28
 
27
29
  module ClassMethods
28
30
  # The cluster config must be defined before `use_cluster`.
29
- # @param [Symbol] A cluster name which is set by MixedGauge.configure
31
+ # @param [Symbol] name A cluster name which is set by MixedGauge.configure
30
32
  def use_cluster(name)
31
33
  config = MixedGauge.config.fetch_cluster_config(name)
32
34
  self.cluster_routing = MixedGauge::Routing.new(config)
@@ -41,6 +43,12 @@ module MixedGauge
41
43
  self.distkey = column.to_sym
42
44
  end
43
45
 
46
+ # @param [Hash{Symbol => Symbol}] mapping A pairs of role name and
47
+ # AR model class name.
48
+ def replicates_with(mapping)
49
+ self.replication_mapping = MixedGauge::ReplicationMapping.new(mapping)
50
+ end
51
+
44
52
  # Create new record with given attributes in proper shard for given key.
45
53
  # When distkey value is empty, raises MixedGauge::MissingDistkeyAttribute
46
54
  # error.
@@ -118,6 +126,21 @@ module MixedGauge
118
126
  end
119
127
  alias_method :parallel, :all_shards_in_parallel
120
128
 
129
+ # See example definitions in `spec/models.rb`.
130
+ # @param [Symbol] A role name of target cluster.
131
+ # @return [Class, Object] if block given then yielded result else
132
+ # target shard model.
133
+ # @example
134
+ # UserReadonly.all_shards.each do |m|
135
+ # target_ids = m.where(age: 1).pluck(:id)
136
+ # m.switch(:master) do |master|
137
+ # master.where(id: target_ids).delete_all
138
+ # end
139
+ # end
140
+ def switch(role_name, &block)
141
+ replication_mapping.switch(self, role_name, &block)
142
+ end
143
+
121
144
  # Define utility methods which uses all shards or specific shard.
122
145
  # These methods can be called from included model class.
123
146
  # @example
@@ -0,0 +1,35 @@
1
+ module MixedGauge
2
+ class ReplicationMapping
3
+ def initialize(mapping)
4
+ @mapping = mapping
5
+ @lock = Mutex.new
6
+ end
7
+
8
+ # @param [Class] A shard model having connection to specific shard
9
+ # @param [Symbol] A role name of target cluster.
10
+ # @return [Class, Object] if block given then yielded result else
11
+ # target shard model.
12
+ def switch(from, role_name, &block)
13
+ @lock.synchronize { constantize! unless constantized? }
14
+
15
+ model = @mapping.fetch(role_name)
16
+ target_shard_model = model.shard_repository.fetch_by_slots(from.assigned_slots)
17
+
18
+ if block_given?
19
+ target_shard_model.connection_pool.with_connection { yield target_shard_model }
20
+ else
21
+ target_shard_model
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def constantize!
28
+ @mapping = Hash[@mapping.map {|k, name| [k, name.to_s.constantize] }]
29
+ end
30
+
31
+ def constantized?
32
+ @mapping.values.first.is_a? Class
33
+ end
34
+ end
35
+ end
@@ -7,8 +7,8 @@ module MixedGauge
7
7
  def initialize(cluster_config, base_class)
8
8
  @base_class = base_class
9
9
 
10
- shards = cluster_config.connections.map do |connection_name|
11
- [connection_name, generate_model_for_shard(connection_name)]
10
+ shards = cluster_config.connection_registry.map do |slot_range, connection_name|
11
+ [connection_name, generate_model_for_shard(connection_name, slot_range)]
12
12
  end
13
13
  @shards = Hash[shards]
14
14
  end
@@ -19,6 +19,12 @@ module MixedGauge
19
19
  @shards.fetch(connection_name)
20
20
  end
21
21
 
22
+ # @param [Range] slots
23
+ # @return [Class, nil] A AR model class.
24
+ def fetch_by_slots(assigned_slots)
25
+ @shards.find {|_, model| model.assigned_slots == assigned_slots }[1]
26
+ end
27
+
22
28
  # @return [Array<Class>]
23
29
  def all
24
30
  @shards.values
@@ -27,13 +33,19 @@ module MixedGauge
27
33
  private
28
34
 
29
35
  # @param [Symbol] connection_name
36
+ # @param [Range] slot_range
30
37
  # @return [Class] A sub class of given AR model.
31
38
  # A sub class has connection setting for specific shard.
32
- def generate_model_for_shard(connection_name)
39
+ def generate_model_for_shard(connection_name, slot_range)
33
40
  base_class_name = @base_class.name
34
41
  class_name = generate_class_name(connection_name)
35
42
 
36
43
  model = Class.new(base_class) do
44
+ class << self
45
+ attr_reader :assigned_slots
46
+ end
47
+ @assigned_slots = slot_range
48
+
37
49
  self.table_name = base_class.table_name
38
50
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
39
51
  def self.name
@@ -1,3 +1,3 @@
1
1
  module MixedGauge
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixed_gauge
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taiki Ono
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-06-24 00:00:00.000000000 Z
11
+ date: 2016-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -198,6 +198,7 @@ files:
198
198
  - lib/mixed_gauge/errors.rb
199
199
  - lib/mixed_gauge/model.rb
200
200
  - lib/mixed_gauge/railtie.rb
201
+ - lib/mixed_gauge/replication_mapping.rb
201
202
  - lib/mixed_gauge/routing.rb
202
203
  - lib/mixed_gauge/shard_repository.rb
203
204
  - lib/mixed_gauge/version.rb
@@ -223,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
224
  version: '0'
224
225
  requirements: []
225
226
  rubyforge_project:
226
- rubygems_version: 2.4.5
227
+ rubygems_version: 2.6.3
227
228
  signing_key:
228
229
  specification_version: 4
229
230
  summary: A simple and robust ActiveRecord extension for database sharding.