sequel-schema-sharding 0.2.0 → 0.3.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: 9259594ee38417f3162a7b250722bb2f6d784b4b
4
- data.tar.gz: 7ab7c107c400e6ec46d3cd612bda9097c82e8243
3
+ metadata.gz: 2c3fdaa18a8bee064030a25babedc4e261c3e160
4
+ data.tar.gz: 5b40cc67b014aa243ae76ef915a53cafcfc82c16
5
5
  SHA512:
6
- metadata.gz: e1c5b3345a5ce05accb17231d8981a61b077cfe30f653992dbcfefb71c52689b31c1d9248ac831a2d857fb30fa326bbf383535b2ae5615600e7cc0e734316e2b
7
- data.tar.gz: 47367a85fe138b51f66a8a60368ef2faf960d4c539117cc972abfc32770562cbab770a29b07cf697892d61d9d3eb07f48c3c422eca68975fa90a021bb6e61b9b
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_%e_%s"
38
+ schema_name: "schema_%04d"
39
39
  logical_shards:
40
- <1..n>: <shard_name>
41
- <n+1..m>: <shard_name>
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, `%e` is replaced with the current environment, and `%s` is
53
- replaced by the shard number.
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
- TODO: explain why we define `this`
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
 
@@ -1,12 +1,12 @@
1
1
  test:
2
2
  tables:
3
3
  artists:
4
- schema_name: artists_%e_%s
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_%e_%s
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_%e_%s
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_%e_%s
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
- table_configs = config['tables'][table_name]
25
- raise "Unknown table #{table_name} in configuration" if table_configs.nil?
26
- table_configs['logical_shards'].inject({}) do |hash, value|
27
- eval(value[0]).each do |i|
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, environment, shard_number)
29
- config.schema_name(table_name).gsub('%e', environment).gsub('%s', shard_number.to_s)
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, ENV['RACK_ENV'], shard_number)}__#{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, env, shard_number)
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, env, shard_number)
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, config.env, shard_number)
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,5 +1,5 @@
1
1
  module Sequel
2
2
  module SchemaSharding
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -1,15 +1,17 @@
1
1
  test:
2
2
  tables:
3
3
  artists:
4
- schema_name: sequel_logical_artists_%e_%s
4
+ schema_name: sequel_logical_artists_%02d
5
+ number_of_shards: 20
5
6
  logical_shards:
6
- 1..10: shard1
7
- 11..20: shard2
7
+ shard1: 1..10
8
+ shard2: 11..20
8
9
  boof:
9
- schema_name: sequel_logical_boof_%e_%s
10
+ schema_name: sequel_logical_boof_%02d
11
+ number_of_shards: 20
10
12
  logical_shards:
11
- 1..10: shard1
12
- 11..20: shard2
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_%e_%s
30
+ schema_name: sequel_explosions_artists_%02d
31
+ number_of_shards: 20
29
32
  logical_shards:
30
- 1..10: shard1
31
- 11..20: shard2
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_%e_%s
36
+ schema_name: sequel_explosions_boof_%02d
37
+ number_of_shards: 20
34
38
  logical_shards:
35
- 1..10: shard1
36
- 11..20: shard2
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', 'pickles', 3).should eq 'sequel_logical_boof_pickles_3'
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(:'sequel_logical_artists_test_1__artists')
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', 'sequel_explosions_boof_boom_1')).to be_false
89
- expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_boom_1')).to be_false
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', 'sequel_explosions_boof_boom_1')).to be_true
92
- expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_boom_1')).to be_true
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', 'sequel_explosions_boof_boom_1')).to be_false
98
- expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_boom_1')).to be_false
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', 'sequel_explosions_boof_boom_1')).to be_true
101
- expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_boom_1')).to be_true
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', 'sequel_explosions_boof_boom_1')).to be_false
113
- expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_boom_1')).to be_false
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', 'sequel_explosions_boof_boom_1')).to be_true
116
- expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_boom_1')).to be_true
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', 'sequel_explosions_boof_boom_1')).to be_false
119
- expect(DatabaseHelper.schema_exists?('shard1', 'sequel_explosions_artists_boom_1')).to be_false
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('sequel_logical_boof_test_2')
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.2.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-09-27 00:00:00.000000000 Z
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.7
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