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 +4 -4
- data/.travis.yml +0 -1
- data/CHANGELOG.md +3 -0
- data/README.md +106 -4
- data/Rakefile +6 -1
- data/lib/mixed_gauge.rb +1 -0
- data/lib/mixed_gauge/cluster_config.rb +1 -1
- data/lib/mixed_gauge/model.rb +24 -1
- data/lib/mixed_gauge/replication_mapping.rb +35 -0
- data/lib/mixed_gauge/shard_repository.rb +15 -3
- data/lib/mixed_gauge/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db4722c025b0f789f356bc263a7b5abd0e4824e9
|
4
|
+
data.tar.gz: 2b09c8ec6892123fdde93ccd227bf4e21cf65500
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f6b47b28eff87fa760c363acc7943200d2ff9fc12fce05aaf245acaa5a8890053b8cc8a698e4a9153740077bcbd8fcb01c8001f8d94b6e2fda80858c3461eef
|
7
|
+
data.tar.gz: a9c5e57c778f0be58568e237edfcea43db2e65b1452cbb5e4bcecb54e64bb61b59feb4052b8a5c09fef00f432213cb7cb4814c5f280ae2f75e7a471cb3e53e78
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
#
|
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
|
-
|
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
|
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]
|
data/lib/mixed_gauge.rb
CHANGED
@@ -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
|
|
data/lib/mixed_gauge/model.rb
CHANGED
@@ -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.
|
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
|
data/lib/mixed_gauge/version.rb
CHANGED
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.
|
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:
|
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.
|
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.
|