sequel-schema-sharding 0.2.0 → 0.3.0
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 +4 -4
- data/README.md +12 -6
- data/examples/sharding.yml +4 -4
- data/lib/sequel/schema-sharding/configuration.rb +20 -8
- data/lib/sequel/schema-sharding/connection_manager.rb +5 -3
- data/lib/sequel/schema-sharding/database_manager.rb +2 -2
- data/lib/sequel/schema-sharding/finder.rb +1 -1
- data/lib/sequel/schema-sharding/ring.rb +5 -0
- data/lib/sequel/schema-sharding/version.rb +1 -1
- data/spec/fixtures/test_db_config.yml +16 -34
- data/spec/schema-sharding/configuration_spec.rb +9 -0
- data/spec/schema-sharding/connection_manager_spec.rb +2 -2
- data/spec/schema-sharding/database_manager_spec.rb +14 -14
- data/spec/schema-sharding/finder_spec.rb +12 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c3fdaa18a8bee064030a25babedc4e261c3e160
|
4
|
+
data.tar.gz: 5b40cc67b014aa243ae76ef915a53cafcfc82c16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8bd4a45ea47e496a888abceda16c09c482402f6561c0c4e52053e8e36c1b3618af268cd8a406ac47ba86a414391b7ab42b0e2144dc8c6a8406fa4a877172877
|
7
|
+
data.tar.gz: d2c4673374e061cc9d9466f4f06709e8d5518daaa85a7d88465904121a78ebb5e190549ed26cda47248b4adefdcb634eeb3841201837a01dd5bb3fd82c58228e
|
data/README.md
CHANGED
@@ -35,10 +35,10 @@ conventions:
|
|
35
35
|
<env>:
|
36
36
|
tables:
|
37
37
|
<table_name>:
|
38
|
-
schema_name: "schema_%
|
38
|
+
schema_name: "schema_%04d"
|
39
39
|
logical_shards:
|
40
|
-
<1..n
|
41
|
-
<n+1..m
|
40
|
+
<shard_name>: <1..n>
|
41
|
+
<shard_name>:<n+1..m>
|
42
42
|
physical_shards:
|
43
43
|
<shard_name>:
|
44
44
|
host: <hostname>
|
@@ -49,8 +49,9 @@ conventions:
|
|
49
49
|
port: <pg_port>
|
50
50
|
```
|
51
51
|
|
52
|
-
In schema names
|
53
|
-
|
52
|
+
In schema names `%04d` is a ```sprintf``` pattern (http://www.ruby-doc.org/core-2.0.0/Kernel.html#method-i-sprintf), where
|
53
|
+
%d is expanded by passing the shard number. Using the pattern you can zero-pad the shard number, or use another
|
54
|
+
pattern that suites your environment.
|
54
55
|
|
55
56
|
Tables can coexist in schemas, though they do not have to.
|
56
57
|
|
@@ -142,7 +143,12 @@ the correct database connection and shard name is used. Writes will
|
|
142
143
|
automatically choose the correct shard based on the sharded column.
|
143
144
|
Never try to insert records with nil values in sharded columns.
|
144
145
|
|
145
|
-
|
146
|
+
### this
|
147
|
+
|
148
|
+
You must define a `:this` instance method on your model. `:this` must
|
149
|
+
return a dataset that is scoped to the current instance in the database,
|
150
|
+
so that Sequel can update/delete/etc the record when you call methods on
|
151
|
+
the instance that persist, ie `:destroy`, `:save`.
|
146
152
|
|
147
153
|
## Running tests
|
148
154
|
|
data/examples/sharding.yml
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
test:
|
2
2
|
tables:
|
3
3
|
artists:
|
4
|
-
schema_name: artists_%
|
4
|
+
schema_name: artists_%04d
|
5
5
|
logical_shards:
|
6
6
|
1..2: shard1
|
7
7
|
3..4: shard2
|
8
8
|
albums:
|
9
|
-
schema_name: albums_%
|
9
|
+
schema_name: albums_%04d
|
10
10
|
logical_shards:
|
11
11
|
1..2: shard2
|
12
12
|
3..4: shard3
|
@@ -27,12 +27,12 @@ test:
|
|
27
27
|
development:
|
28
28
|
tables:
|
29
29
|
artists:
|
30
|
-
schema_name: artists_%
|
30
|
+
schema_name: artists_%04d
|
31
31
|
logical_shards:
|
32
32
|
1..2: shard1
|
33
33
|
3..4: shard2
|
34
34
|
albums:
|
35
|
-
schema_name: albums_%
|
35
|
+
schema_name: albums_%04d
|
36
36
|
logical_shards:
|
37
37
|
1..2: shard2
|
38
38
|
3..4: shard3
|
@@ -21,15 +21,12 @@ module Sequel
|
|
21
21
|
table_name = table_name.to_s
|
22
22
|
@logical_shard_table_configs ||= {}
|
23
23
|
@logical_shard_table_configs[table_name] ||= begin
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
hash[i] = value[1]
|
29
|
-
end
|
30
|
-
hash
|
31
|
-
end
|
24
|
+
config, number_of_shards = parse_logical_shard_config_for(table_name),
|
25
|
+
number_of_shards(table_name)
|
26
|
+
raise "Shard number mismatch: expected #{number_of_shards} got #{config.size} for table #{table_name}" if config.size != number_of_shards
|
27
|
+
config
|
32
28
|
end
|
29
|
+
|
33
30
|
end
|
34
31
|
|
35
32
|
def table_names
|
@@ -40,8 +37,23 @@ module Sequel
|
|
40
37
|
config['tables'][table_name.to_s]['schema_name']
|
41
38
|
end
|
42
39
|
|
40
|
+
def number_of_shards(table_name)
|
41
|
+
config['tables'][table_name.to_s]['number_of_shards']
|
42
|
+
end
|
43
|
+
|
43
44
|
private
|
44
45
|
|
46
|
+
def parse_logical_shard_config_for(table_name)
|
47
|
+
table_configs = config['tables'][table_name]
|
48
|
+
raise "Unknown table #{table_name} in configuration" if table_configs.nil?
|
49
|
+
table_configs['logical_shards'].inject({}) do |hash, value|
|
50
|
+
eval(value[1]).each do |i|
|
51
|
+
hash[i] = value[0]
|
52
|
+
end
|
53
|
+
hash
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
45
57
|
def config
|
46
58
|
yaml[env.to_s]
|
47
59
|
end
|
@@ -25,14 +25,16 @@ module Sequel
|
|
25
25
|
@connections = {}
|
26
26
|
end
|
27
27
|
|
28
|
-
def schema_for(table_name,
|
29
|
-
config.
|
28
|
+
def schema_for(table_name, shard_number)
|
29
|
+
number_of_shards = config.number_of_shards(table_name)
|
30
|
+
pattern = config.schema_name(table_name)
|
31
|
+
sprintf pattern, shard_number
|
30
32
|
end
|
31
33
|
|
32
34
|
def default_dataset_for(table_name)
|
33
35
|
shard_number = config.logical_shard_configs(table_name).keys.first
|
34
36
|
shard_name = config.logical_shard_configs(table_name)[shard_number]
|
35
|
-
self[shard_name][:"#{schema_for(table_name,
|
37
|
+
self[shard_name][:"#{schema_for(table_name, shard_number)}__#{table_name}"]
|
36
38
|
end
|
37
39
|
|
38
40
|
private
|
@@ -57,7 +57,7 @@ module Sequel
|
|
57
57
|
def create_shards
|
58
58
|
config.table_names.each do |table_name|
|
59
59
|
config.logical_shard_configs(table_name).each_pair do |shard_number, physical_shard|
|
60
|
-
schema_name = connection_manager.schema_for(table_name,
|
60
|
+
schema_name = connection_manager.schema_for(table_name, shard_number)
|
61
61
|
Sequel::SchemaSharding.logger.info "Creating schema #{schema_name} on #{physical_shard}.."
|
62
62
|
connection = connection_manager.master(physical_shard)
|
63
63
|
|
@@ -81,7 +81,7 @@ module Sequel
|
|
81
81
|
def drop_shards
|
82
82
|
config.table_names.each do |table_name|
|
83
83
|
config.logical_shard_configs(table_name).each_pair do |shard_number, physical_shard|
|
84
|
-
schema_name = connection_manager.schema_for(table_name,
|
84
|
+
schema_name = connection_manager.schema_for(table_name, shard_number)
|
85
85
|
Sequel::SchemaSharding.logger.info "Dropping schema #{schema_name} on #{physical_shard}.."
|
86
86
|
connection = connection_manager[physical_shard]
|
87
87
|
connection.run("DROP SCHEMA #{schema_name} CASCADE")
|
@@ -19,7 +19,7 @@ module Sequel
|
|
19
19
|
physical_shard = config.logical_shard_configs(table_name)[shard_number]
|
20
20
|
|
21
21
|
conn = Sequel::SchemaSharding.connection_manager[physical_shard]
|
22
|
-
schema = Sequel::SchemaSharding.connection_manager.schema_for(table_name,
|
22
|
+
schema = Sequel::SchemaSharding.connection_manager.schema_for(table_name, shard_number)
|
23
23
|
|
24
24
|
Result.new(conn, schema)
|
25
25
|
end
|
@@ -3,6 +3,11 @@ require 'zlib'
|
|
3
3
|
|
4
4
|
module Sequel
|
5
5
|
module SchemaSharding
|
6
|
+
# This class is responsible for mapping IDs into shards, using
|
7
|
+
# Ketama consistent hashing algorithm, which makes it easier to
|
8
|
+
# rehash in the future, should the number of shards increase.
|
9
|
+
# For more information see http://en.wikipedia.org/wiki/Consistent_hashing
|
10
|
+
# This implementation is borrowed from Dali memcached library.
|
6
11
|
class Ring
|
7
12
|
POINTS_PER_SERVER = 1
|
8
13
|
|
@@ -1,15 +1,17 @@
|
|
1
1
|
test:
|
2
2
|
tables:
|
3
3
|
artists:
|
4
|
-
schema_name: sequel_logical_artists_%
|
4
|
+
schema_name: sequel_logical_artists_%02d
|
5
|
+
number_of_shards: 20
|
5
6
|
logical_shards:
|
6
|
-
1..10
|
7
|
-
11..20
|
7
|
+
shard1: 1..10
|
8
|
+
shard2: 11..20
|
8
9
|
boof:
|
9
|
-
schema_name: sequel_logical_boof_%
|
10
|
+
schema_name: sequel_logical_boof_%02d
|
11
|
+
number_of_shards: 20
|
10
12
|
logical_shards:
|
11
|
-
1..10
|
12
|
-
11..20
|
13
|
+
shard1: 1..10
|
14
|
+
shard2: 11..20
|
13
15
|
physical_shards:
|
14
16
|
shard1:
|
15
17
|
host: 127.0.0.1
|
@@ -25,15 +27,17 @@ test:
|
|
25
27
|
boom:
|
26
28
|
tables:
|
27
29
|
artists:
|
28
|
-
schema_name: sequel_explosions_artists_%
|
30
|
+
schema_name: sequel_explosions_artists_%02d
|
31
|
+
number_of_shards: 20
|
29
32
|
logical_shards:
|
30
|
-
1..10
|
31
|
-
11..20
|
33
|
+
shard1: "[(1..8).to_a,9,10].flatten" # any expression that evaluates into an array works
|
34
|
+
shard2: 11..20
|
32
35
|
boof:
|
33
|
-
schema_name: sequel_explosions_boof_%
|
36
|
+
schema_name: sequel_explosions_boof_%02d
|
37
|
+
number_of_shards: 20
|
34
38
|
logical_shards:
|
35
|
-
1..10
|
36
|
-
11..20
|
39
|
+
shard1: 1..10
|
40
|
+
shard2: 11..20
|
37
41
|
physical_shards:
|
38
42
|
shard1:
|
39
43
|
host: 127.0.0.1
|
@@ -47,25 +51,3 @@ boom:
|
|
47
51
|
username: postgres
|
48
52
|
password: boomboomkaboom
|
49
53
|
port: 5432
|
50
|
-
#boom:
|
51
|
-
# logical_shards:
|
52
|
-
# user_saves:
|
53
|
-
# 1..10: shard1
|
54
|
-
# 11..20: shard2
|
55
|
-
# product_saves:
|
56
|
-
# 1..10: shard2
|
57
|
-
# 11..20: shard3
|
58
|
-
# physical_shards:
|
59
|
-
# shard1:
|
60
|
-
# host: 127.0.0.1
|
61
|
-
# database: wanelo_saves_boom_shard1
|
62
|
-
# shard2:
|
63
|
-
# host: 127.0.0.1
|
64
|
-
# database: wanelo_saves_boom_shard2
|
65
|
-
# shard3:
|
66
|
-
# host: 127.0.0.1
|
67
|
-
# database: wanelo_saves_boom_shard3
|
68
|
-
# common:
|
69
|
-
# username: postgres
|
70
|
-
# password: boomboomkaboom
|
71
|
-
# port: 5432
|
@@ -19,6 +19,15 @@ describe Sequel::SchemaSharding::Configuration do
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
|
+
|
23
|
+
context 'when the number of shards not the same' do
|
24
|
+
it 'raises an exception' do
|
25
|
+
config.stubs(:number_of_shards).returns(1)
|
26
|
+
expect {
|
27
|
+
config.logical_shard_configs('boof')
|
28
|
+
}.to raise_error(RuntimeError)
|
29
|
+
end
|
30
|
+
end
|
22
31
|
end
|
23
32
|
|
24
33
|
describe '#physical_shard_configs' do
|
@@ -35,7 +35,7 @@ describe Sequel::SchemaSharding::ConnectionManager do
|
|
35
35
|
|
36
36
|
describe "#schema_for" do
|
37
37
|
it "returns the schema name based on env and shard number" do
|
38
|
-
subject.schema_for('boof',
|
38
|
+
subject.schema_for('boof', 3).should eq 'sequel_logical_boof_03'
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -45,7 +45,7 @@ describe Sequel::SchemaSharding::ConnectionManager do
|
|
45
45
|
# This should be deconstructed to allow for injection of a mock config for testing.
|
46
46
|
dataset = subject.default_dataset_for("artists")
|
47
47
|
expect(dataset).to be_a(Sequel::Dataset)
|
48
|
-
expect(dataset.first_source_table).to eql(:'
|
48
|
+
expect(dataset.first_source_table).to eql(:'sequel_logical_artists_01__artists')
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
@@ -85,20 +85,20 @@ describe Sequel::SchemaSharding::DatabaseManager, type: :manager, sharded: true
|
|
85
85
|
|
86
86
|
describe '#create_shards' do
|
87
87
|
it 'creates the database structure' do
|
88
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
89
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
88
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_boof_01')).to be_false
|
89
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_01')).to be_false
|
90
90
|
@manager.create_shards
|
91
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
92
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
91
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_boof_01')).to be_true
|
92
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_01')).to be_true
|
93
93
|
end
|
94
94
|
|
95
95
|
context 'shards already exist' do
|
96
96
|
it 'prints that shards already exist' do
|
97
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
98
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
97
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_boof_01')).to be_false
|
98
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_01')).to be_false
|
99
99
|
@manager.create_shards
|
100
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
101
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
100
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_boof_01')).to be_true
|
101
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_01')).to be_true
|
102
102
|
|
103
103
|
$stderr.expects(:puts).with(regexp_matches(/already exists/)).at_least_once
|
104
104
|
@manager.create_shards
|
@@ -109,14 +109,14 @@ describe Sequel::SchemaSharding::DatabaseManager, type: :manager, sharded: true
|
|
109
109
|
describe '#drop_schemas' do
|
110
110
|
context 'schemas exist' do
|
111
111
|
it 'drops the schemas' do
|
112
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
113
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
112
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_boof_01')).to be_false
|
113
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_01')).to be_false
|
114
114
|
@manager.create_shards
|
115
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
116
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
115
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_boof_01')).to be_true
|
116
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_01')).to be_true
|
117
117
|
@manager.drop_shards
|
118
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
119
|
-
expect(DatabaseHelper.schema_exists?('shard1', '
|
118
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_boof_01')).to be_false
|
119
|
+
expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_01')).to be_false
|
120
120
|
end
|
121
121
|
end
|
122
122
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'sequel/schema-sharding'
|
3
|
+
require 'benchmark'
|
3
4
|
|
4
5
|
describe Sequel::SchemaSharding::Finder do
|
5
6
|
|
@@ -7,7 +8,17 @@ describe Sequel::SchemaSharding::Finder do
|
|
7
8
|
it 'returns an object with a valid connection and schema' do
|
8
9
|
result = Sequel::SchemaSharding::Finder.instance.lookup('boof', 60)
|
9
10
|
expect(result.connection).to be_a(Sequel::Postgres::Database)
|
10
|
-
expect(result.schema).to eq('
|
11
|
+
expect(result.schema).to eq('sequel_logical_boof_02')
|
12
|
+
end
|
13
|
+
|
14
|
+
xit 'is fast' do
|
15
|
+
TIMES = 150_000
|
16
|
+
result = Benchmark.measure do
|
17
|
+
TIMES.times do
|
18
|
+
Sequel::SchemaSharding::Finder.instance.lookup('boof', 60)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
puts "performed #{TIMES} finder lookups: #{result}"
|
11
22
|
end
|
12
23
|
end
|
13
24
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel-schema-sharding
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Henry
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-10-01 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: sequel
|
@@ -132,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
132
|
version: '0'
|
133
133
|
requirements: []
|
134
134
|
rubyforge_project:
|
135
|
-
rubygems_version: 2.0.
|
135
|
+
rubygems_version: 2.0.3
|
136
136
|
signing_key:
|
137
137
|
specification_version: 4
|
138
138
|
summary: Create horizontally sharded Sequel models with Postgres
|