activerecord-turntable 1.1.2 → 2.0.0.rc1
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/.gitignore +5 -0
- data/.travis.yml +3 -5
- data/CHANGELOG.md +70 -0
- data/Guardfile +7 -5
- data/README.md +490 -0
- data/Rakefile +37 -22
- data/activerecord-turntable.gemspec +37 -34
- data/gemfiles/rails4_0.gemfile +6 -0
- data/gemfiles/rails4_1.gemfile +6 -0
- data/lib/active_record/turntable/active_record_ext/abstract_adapter.rb +14 -29
- data/lib/active_record/turntable/active_record_ext/activerecord_import_ext.rb +45 -0
- data/lib/active_record/turntable/active_record_ext/acts_as_archive_extension.rb +21 -0
- data/lib/active_record/turntable/active_record_ext/association.rb +85 -0
- data/lib/active_record/turntable/active_record_ext/association_preloader.rb +37 -0
- data/lib/active_record/turntable/active_record_ext/clever_load.rb +33 -76
- data/lib/active_record/turntable/active_record_ext/connection_handler_extension.rb +31 -0
- data/lib/active_record/turntable/active_record_ext/database_tasks.rb +81 -0
- data/lib/active_record/turntable/active_record_ext/fixtures.rb +54 -42
- data/lib/active_record/turntable/active_record_ext/locking_optimistic.rb +101 -0
- data/lib/active_record/turntable/active_record_ext/log_subscriber.rb +28 -46
- data/lib/active_record/turntable/active_record_ext/migration_proxy.rb +7 -0
- data/lib/active_record/turntable/active_record_ext/persistence.rb +96 -94
- data/lib/active_record/turntable/active_record_ext/relation.rb +31 -0
- data/lib/active_record/turntable/active_record_ext/schema_dumper.rb +18 -28
- data/lib/active_record/turntable/active_record_ext/transactions.rb +9 -3
- data/lib/active_record/turntable/active_record_ext.rb +26 -11
- data/lib/active_record/turntable/algorithm/base.rb +1 -1
- data/lib/active_record/turntable/algorithm/range_bsearch_algorithm.rb +1 -1
- data/lib/active_record/turntable/algorithm.rb +7 -3
- data/lib/active_record/turntable/base.rb +67 -14
- data/lib/active_record/turntable/cluster.rb +46 -2
- data/lib/active_record/turntable/config.rb +1 -1
- data/lib/active_record/turntable/connection_proxy/mixable.rb +7 -29
- data/lib/active_record/turntable/connection_proxy.rb +61 -72
- data/lib/active_record/turntable/error.rb +5 -6
- data/lib/active_record/turntable/helpers.rb +5 -1
- data/lib/active_record/turntable/migration.rb +9 -49
- data/lib/active_record/turntable/mixer/fader/calculate_shards_sum_result.rb +13 -2
- data/lib/active_record/turntable/mixer/fader/select_shards_merge_result.rb +17 -6
- data/lib/active_record/turntable/mixer/fader/specified_shard.rb +3 -1
- data/lib/active_record/turntable/mixer/fader.rb +12 -10
- data/lib/active_record/turntable/mixer.rb +59 -29
- data/lib/active_record/turntable/plugin.rb +6 -0
- data/lib/active_record/turntable/pool_proxy.rb +12 -19
- data/lib/active_record/turntable/rack/query_cache.rb +20 -23
- data/lib/active_record/turntable/rack.rb +4 -2
- data/lib/active_record/turntable/railtie.rb +4 -3
- data/lib/active_record/turntable/railties/databases.rake +81 -122
- data/lib/active_record/turntable/seq_shard.rb +1 -1
- data/lib/active_record/turntable/sequencer/api.rb +1 -1
- data/lib/active_record/turntable/sequencer/barrage.rb +28 -0
- data/lib/active_record/turntable/sequencer.rb +27 -9
- data/lib/active_record/turntable/shard.rb +2 -2
- data/lib/active_record/turntable/sql_tree_patch.rb +1 -1
- data/lib/active_record/turntable/version.rb +1 -1
- data/lib/active_record/turntable.rb +26 -16
- data/lib/generators/templates/turntable.yml +9 -7
- data/spec/active_record/turntable/active_record_ext/association_preloader_spec.rb +78 -0
- data/spec/active_record/turntable/active_record_ext/association_spec.rb +72 -0
- data/spec/active_record/turntable/active_record_ext/clever_load_spec.rb +25 -46
- data/spec/active_record/turntable/active_record_ext/locking_optimistic_spec.rb +28 -0
- data/spec/active_record/turntable/active_record_ext/persistence_spec.rb +46 -25
- data/spec/active_record/turntable/algorithm/range_algorithm_spec.rb +4 -4
- data/spec/active_record/turntable/algorithm/range_bsearch_algorithm_spec.rb +35 -0
- data/spec/active_record/turntable/algorithm_spec.rb +28 -12
- data/spec/active_record/turntable/base_spec.rb +1 -1
- data/spec/active_record/turntable/cluster_spec.rb +27 -5
- data/spec/active_record/turntable/config_spec.rb +2 -2
- data/spec/active_record/turntable/connection_proxy_spec.rb +112 -45
- data/spec/active_record/turntable/finder_spec.rb +24 -11
- data/spec/active_record/turntable/mixer_spec.rb +21 -21
- data/spec/active_record/turntable/rack/query_cache_spec.rb +19 -0
- data/spec/active_record/turntable/sequencer/api_spec.rb +38 -0
- data/spec/active_record/turntable/sequencer/barrage_spec.rb +22 -0
- data/spec/active_record/turntable/sequencer/mysql_spec.rb +22 -0
- data/spec/active_record/turntable/shard_spec.rb +1 -1
- data/spec/active_record/turntable/transaction_spec.rb +35 -0
- data/spec/active_record/turntable_spec.rb +4 -4
- data/spec/config/database.yml +24 -34
- data/spec/config/turntable.yml +18 -1
- data/spec/fabricators/turntable_fabricator.rb +0 -2
- data/spec/models/card.rb +3 -0
- data/spec/models/cards_user.rb +10 -0
- data/spec/models/cards_users_histories.rb +7 -0
- data/spec/models/events_users_history.rb +7 -0
- data/spec/models/user.rb +7 -0
- data/spec/models/user_status.rb +6 -0
- data/spec/spec_helper.rb +10 -4
- data/spec/support/matchers/be_saved_to.rb +6 -0
- data/spec/support/turntable_helper.rb +29 -0
- metadata +124 -74
- data/README.rdoc +0 -294
- data/gemfiles/rails3_0.gemfile +0 -7
- data/gemfiles/rails3_1.gemfile +0 -6
- data/gemfiles/rails3_2.gemfile +0 -6
- data/lib/active_record/turntable/compatible.rb +0 -19
- data/sample_app/.gitignore +0 -16
- data/sample_app/Gemfile +0 -41
- data/sample_app/README.rdoc +0 -261
- data/sample_app/Rakefile +0 -7
- data/sample_app/app/assets/images/rails.png +0 -0
- data/sample_app/app/assets/javascripts/application.js +0 -15
- data/sample_app/app/assets/stylesheets/application.css +0 -13
- data/sample_app/app/controllers/application_controller.rb +0 -3
- data/sample_app/app/helpers/application_helper.rb +0 -2
- data/sample_app/app/mailers/.gitkeep +0 -0
- data/sample_app/app/models/.gitkeep +0 -0
- data/sample_app/app/models/user.rb +0 -4
- data/sample_app/app/views/layouts/application.html.erb +0 -14
- data/sample_app/config/application.rb +0 -65
- data/sample_app/config/boot.rb +0 -6
- data/sample_app/config/database.yml +0 -70
- data/sample_app/config/environment.rb +0 -5
- data/sample_app/config/environments/development.rb +0 -37
- data/sample_app/config/environments/production.rb +0 -67
- data/sample_app/config/environments/test.rb +0 -37
- data/sample_app/config/initializers/backtrace_silencers.rb +0 -7
- data/sample_app/config/initializers/inflections.rb +0 -15
- data/sample_app/config/initializers/mime_types.rb +0 -5
- data/sample_app/config/initializers/secret_token.rb +0 -7
- data/sample_app/config/initializers/session_store.rb +0 -8
- data/sample_app/config/initializers/wrap_parameters.rb +0 -14
- data/sample_app/config/locales/en.yml +0 -5
- data/sample_app/config/routes.rb +0 -58
- data/sample_app/config/turntable.yml +0 -64
- data/sample_app/config.ru +0 -4
- data/sample_app/db/migrate/20120316073058_create_users.rb +0 -11
- data/sample_app/db/seeds.rb +0 -7
- data/sample_app/lib/assets/.gitkeep +0 -0
- data/sample_app/lib/tasks/.gitkeep +0 -0
- data/sample_app/log/.gitkeep +0 -0
- data/sample_app/public/404.html +0 -26
- data/sample_app/public/422.html +0 -26
- data/sample_app/public/500.html +0 -25
- data/sample_app/public/favicon.ico +0 -0
- data/sample_app/public/index.html +0 -241
- data/sample_app/public/robots.txt +0 -5
- data/sample_app/script/rails +0 -6
- data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/sample_app/vendor/plugins/.gitkeep +0 -0
- data/spec/test_models.rb +0 -27
- data/spec/turntable_helper.rb +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d2ed24b4fc25e1ec4752797494ce5f7e01aba00
|
|
4
|
+
data.tar.gz: e9686336be4cac05c60c3d129d3b94c3d562674c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 948dc1c9c05cedd33df32a763b79a587ec7e748ef03e96b8b763b7e991716b82f619c2bfbb4e4980136ae507c78dda9e06b733768613ecca2468ce8bd33042f3
|
|
7
|
+
data.tar.gz: 887ec36800641c5f632acdc83cc8abcb22f428ee196743e1d64b8b371d93e99fedc776d4c4151b91b57fc8586da80f91692f75c5249a354b076a90060e423f6e
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
language: ruby
|
|
2
2
|
rvm:
|
|
3
|
-
- 1.9.2
|
|
4
3
|
- 1.9.3
|
|
5
4
|
- 2.0.0
|
|
5
|
+
- 2.1.2
|
|
6
6
|
gemfile:
|
|
7
|
-
- gemfiles/
|
|
8
|
-
- gemfiles/
|
|
9
|
-
- gemfiles/rails3_2.gemfile
|
|
7
|
+
- gemfiles/rails4_0.gemfile
|
|
8
|
+
- gemfiles/rails4_1.gemfile
|
|
10
9
|
before_script:
|
|
11
10
|
- bundle exec rake turntable:db:reset
|
|
12
11
|
script: bundle exec rake spec
|
|
13
|
-
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,73 @@
|
|
|
1
|
+
## activerecord-turntable 2.0.0 (not released yet) ##
|
|
2
|
+
|
|
3
|
+
First release for ActiveRecord 4.x
|
|
4
|
+
|
|
5
|
+
### Incompatible Changes
|
|
6
|
+
|
|
7
|
+
#### Support ActiveRecord 4.x, drop support AR 3.x
|
|
8
|
+
|
|
9
|
+
If you are using AR 3.x, use turntable 1.x.
|
|
10
|
+
|
|
11
|
+
#### Default, Migration will be executed to all shards
|
|
12
|
+
|
|
13
|
+
Migration had been executed to only master database on turntable 1.x.
|
|
14
|
+
|
|
15
|
+
On turntable 2.x, migration is executed to all shards as default behavior.
|
|
16
|
+
|
|
17
|
+
#### Multiple Sequencer
|
|
18
|
+
|
|
19
|
+
Multiple sequencers supported.
|
|
20
|
+
|
|
21
|
+
Please pass sequencer's name Symbol to `sequencer` DSL:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
sequencer :user_seq
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
#### Barrage sequencer
|
|
30
|
+
|
|
31
|
+
Supported [barrage](http://github.com/drecom/barrage) gem as sequencer
|
|
32
|
+
|
|
33
|
+
#### Better association support
|
|
34
|
+
|
|
35
|
+
When using association(or association preloading), Turntable would add shard key condition to relation object if associated models has the same shard key name.
|
|
36
|
+
|
|
37
|
+
If two related models has different named keys(but same meaning), you can pass option `foreign_shard_key` to association option.
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
class UserReceivedItemHistory < ActiveRecord::Base
|
|
43
|
+
has_many :user, foreign_shard_key: :receiver_user_id
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### Exception on performance notice
|
|
48
|
+
|
|
49
|
+
On development environment, you can receive exception about queries that may cause a performance problem.
|
|
50
|
+
|
|
51
|
+
Add follow option to `turntable.yml`:
|
|
52
|
+
|
|
53
|
+
* raise\_on\_not\_specified\_shard\_query(default: false)
|
|
54
|
+
* raise\_on\_not\_specified\_shard\_update(default: false)
|
|
55
|
+
|
|
56
|
+
#### Add cluster transaction
|
|
57
|
+
|
|
58
|
+
To create transaction to all shards on the cluster:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
User.user_cluster_transaction do
|
|
62
|
+
# transaction opened on all shards in 'user_cluster'
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Bugfixes
|
|
67
|
+
|
|
68
|
+
* Fix thread-safety bug
|
|
69
|
+
|
|
70
|
+
|
|
1
71
|
## activerecord-turntable 1.1.2 ##
|
|
2
72
|
|
|
3
73
|
* Increase sequence table performance (by hanabokuro)
|
data/Guardfile
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# A sample Guardfile
|
|
2
2
|
# More info at https://github.com/guard/guard#readme
|
|
3
3
|
|
|
4
|
-
guard 'rspec',
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
guard 'rspec',
|
|
5
|
+
cmd: "bundle exec rspec",
|
|
6
|
+
all_after_pass: true,
|
|
7
|
+
all_on_start: true do
|
|
8
|
+
watch(%r{^spec/.+_spec\.rb$})
|
|
9
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
|
10
|
+
watch('spec/spec_helper.rb') { "spec" }
|
|
8
11
|
end
|
|
9
|
-
|
data/README.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# ActiveRecord::Turntable
|
|
2
|
+
|
|
3
|
+
[](http://badge.fury.io/rb/activerecord-turntable)
|
|
4
|
+
[](https://travis-ci.org/drecom/activerecord-turntable)
|
|
5
|
+
[](https://gemnasium.com/drecom/activerecord-turntable)
|
|
6
|
+
[](https://coveralls.io/r/drecom/activerecord-turntable?branch=master)
|
|
7
|
+
|
|
8
|
+
ActiveRecord::Turntable is a database sharding extension for ActiveRecord.
|
|
9
|
+
|
|
10
|
+
## Dependencies
|
|
11
|
+
|
|
12
|
+
activerecord(>=4.0.0)
|
|
13
|
+
|
|
14
|
+
if you are using activerecord 3.x, please use activerecord-turntable version 1.x.
|
|
15
|
+
|
|
16
|
+
## Supported Database
|
|
17
|
+
|
|
18
|
+
Currently supports mysql only.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Add to Gemfile:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem 'activerecord-turntable', '~> 2.0.0.beta'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run a bundle install:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
bundle install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Run install generator:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bundle exec rails g active_record:turntable:install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
generator creates `#{Rails.root}/config/turntable.yml`
|
|
41
|
+
|
|
42
|
+
## Terminologies
|
|
43
|
+
|
|
44
|
+
### Shard
|
|
45
|
+
|
|
46
|
+
Shard is a database which is horizontal partitioned.
|
|
47
|
+
|
|
48
|
+
### Cluster
|
|
49
|
+
|
|
50
|
+
Cluster of shards. i.e) set of userdb1, userdb2, userdb3
|
|
51
|
+
Shards in the same cluster should have the same schema structure.
|
|
52
|
+
|
|
53
|
+
### Master
|
|
54
|
+
|
|
55
|
+
Default ActiveRecord::Base's connection.
|
|
56
|
+
|
|
57
|
+
### Sequencer
|
|
58
|
+
|
|
59
|
+
Turntable's sequence system for clustered database.
|
|
60
|
+
|
|
61
|
+
This keeps primary key ids to be unique each shards.
|
|
62
|
+
|
|
63
|
+
## Example
|
|
64
|
+
|
|
65
|
+
### Example Databases Structure
|
|
66
|
+
|
|
67
|
+
One main database(default ActiveRecord::Base connection) and
|
|
68
|
+
three user databases sharded by user_id.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
+-------+
|
|
72
|
+
| App |
|
|
73
|
+
+-------+
|
|
74
|
+
|
|
|
75
|
+
+---------+---------+---------+---------+
|
|
76
|
+
| | | | |
|
|
77
|
+
`--------` `-------` `-------` `-------` `-------`
|
|
78
|
+
| Master | |UserDB1| |UserDB2| |UserDB3| | SeqDB |
|
|
79
|
+
`--------` `-------` `-------` `-------` `-------`
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Example Configuration
|
|
84
|
+
|
|
85
|
+
Edit turntable.yml and database.yml. See below example config.
|
|
86
|
+
|
|
87
|
+
* example turntable.yml
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
development:
|
|
91
|
+
clusters:
|
|
92
|
+
user_cluster: # <-- cluster name
|
|
93
|
+
algorithm: range_bsearch # <-- `range` or `range_bsearch`
|
|
94
|
+
seq:
|
|
95
|
+
user_seq: # <-- sequencer name
|
|
96
|
+
seq_type: mysql # <-- sequencer type
|
|
97
|
+
connection: user_seq_1 # <-- sequencer database connection setting
|
|
98
|
+
shards:
|
|
99
|
+
- connection: user_shard_1 # <-- shard name
|
|
100
|
+
less_than: 100 # <-- shard range(like mysql partitioning)
|
|
101
|
+
- connection: user_shard_2
|
|
102
|
+
less_than: 200
|
|
103
|
+
- connection: user_shard_3
|
|
104
|
+
less_than: 2000000000
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
* database.yml
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
connection_spec: &spec
|
|
112
|
+
adapter: mysql2
|
|
113
|
+
encoding: utf8
|
|
114
|
+
reconnect: false
|
|
115
|
+
pool: 5
|
|
116
|
+
username: root
|
|
117
|
+
password: root
|
|
118
|
+
socket: /tmp/mysql.sock
|
|
119
|
+
|
|
120
|
+
development:
|
|
121
|
+
<<: *spec
|
|
122
|
+
database: sample_app_development
|
|
123
|
+
seq: # <-- sequence database definition
|
|
124
|
+
user_seq_1:
|
|
125
|
+
<<: *spec
|
|
126
|
+
database: sample_app_user_seq_development
|
|
127
|
+
shards: # <-- shards definition
|
|
128
|
+
user_shard_1:
|
|
129
|
+
<<: *spec
|
|
130
|
+
database: sample_app_user1_development
|
|
131
|
+
user_shard_2:
|
|
132
|
+
<<: *spec
|
|
133
|
+
database: sample_app_user2_development
|
|
134
|
+
user_shard_3:
|
|
135
|
+
<<: *spec
|
|
136
|
+
database: sample_app_user3_development
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Example Migration
|
|
140
|
+
|
|
141
|
+
Generate a model:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
bundle exec rails g model user name:string
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
And Edit migration file:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
class CreateUsers < ActiveRecord::Migration
|
|
151
|
+
# Specify cluster executes migration if you need.
|
|
152
|
+
# Default, migration would be executed to all databases.
|
|
153
|
+
# clusters :user_cluster
|
|
154
|
+
|
|
155
|
+
def change
|
|
156
|
+
create_table :users do |t|
|
|
157
|
+
t.string :name
|
|
158
|
+
t.timestamps
|
|
159
|
+
end
|
|
160
|
+
create_sequence_for(:users) # <-- create sequence table
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Then please execute rake tasks:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
bundle exec rake db:create
|
|
169
|
+
bundle exec rake db:migrate
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Those rake tasks would be executed to shards too.
|
|
173
|
+
|
|
174
|
+
### Example Model
|
|
175
|
+
|
|
176
|
+
Add turntable [shard_key_name] to the model class:
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
class User < ActiveRecord::Base
|
|
180
|
+
turntable :user_cluster, :id
|
|
181
|
+
sequencer
|
|
182
|
+
has_one :status
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
class Status < ActiveRecord::Base
|
|
186
|
+
turntable :user_cluster, :user_id
|
|
187
|
+
sequencer
|
|
188
|
+
belongs_to :user
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Usage
|
|
193
|
+
|
|
194
|
+
### Creating
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
> User.create(name: "hoge")
|
|
198
|
+
(0.0ms) [Shard: user_seq_1] BEGIN
|
|
199
|
+
(0.3ms) [Shard: user_seq_1] UPDATE `users_id_seq` SET id=LAST_INSERT_ID(id+1)
|
|
200
|
+
(0.8ms) [Shard: user_seq_1] COMMIT
|
|
201
|
+
(0.1ms) [Shard: user_seq_1] SELECT LAST_INSERT_ID()
|
|
202
|
+
(0.1ms) [Shard: user_shard_1] BEGIN
|
|
203
|
+
[ActiveRecord::Turntable] Sending method: insert, sql: #<Arel::InsertManager:0x007f8503685b48>, shards: ["user_shard_1"]
|
|
204
|
+
SQL (0.8ms) [Shard: user_shard_1] INSERT INTO `users` (`created_at`, `id`, `name`, `updated_at`) VALUES ('2012-04-10 03:59:42', 2, 'hoge', '2012-04-10 03:59:42')
|
|
205
|
+
(0.4ms) [Shard: user_shard_1] COMMIT
|
|
206
|
+
=> #<User id: 2, name: "hoge", created_at: "2012-04-10 03:59:42", updated_at: "2012-04-10 03:59:42">
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Retrieving
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
> user = User.find(2)
|
|
213
|
+
[ActiveRecord::Turntable] Sending method: select_all, sql: #<Arel::SelectManager:0x007f850466e668>, shards: ["user_shard_1"]
|
|
214
|
+
User Load (0.3ms) [Shard: user_shard_1] SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
|
|
215
|
+
=> #<User id: 2, name: "hoge", created_at: "2012-04-10 03:59:42", updated_at: "2012-04-10 03:59:42">
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Updating
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
> user.update_attributes(name: "hogefoo")
|
|
222
|
+
(0.1ms) [Shard: user_shard_1] BEGIN
|
|
223
|
+
[ActiveRecord::Turntable] Sending method: update, sql: UPDATE `users` SET `name` = 'hogefoo', `updated_at` = '2012-04-10 04:07:52' WHERE `users`.`id` = 2, shards: ["user_shard_1"]
|
|
224
|
+
(0.3ms) [Shard: user_shard_1] UPDATE `users` SET `name` = 'hogefoo', `updated_at` = '2012-04-10 04:07:52' WHERE `users`.`id` = 2
|
|
225
|
+
(0.8ms) [Shard: user_shard_1] COMMIT
|
|
226
|
+
=> true
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Delete
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
> user.destroy
|
|
233
|
+
(0.2ms) [Shard: user_shard_1] BEGIN
|
|
234
|
+
[ActiveRecord::Turntable] Sending method: delete, sql: #<Arel::DeleteManager:0x007f8503677ea8>, shards: ["user_shard_1"]
|
|
235
|
+
SQL (0.3ms) [Shard: user_shard_1] DELETE FROM `users` WHERE `users`.`id` = 2
|
|
236
|
+
(1.7ms) [Shard: user_shard_1] COMMIT
|
|
237
|
+
=> #<User id: 2, name: "hogefoo", created_at: "2012-04-10 03:59:42", updated_at: "2012-04-10 04:07:52">
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Counting
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
> User.count
|
|
244
|
+
[ActiveRecord::Turntable] Sending method: select_value, sql: #<Arel::SelectManager:0x007f9e82ccebb0>, shards: ["user_shard_1", "user_shard_2", "user_shard_3"]
|
|
245
|
+
(0.8ms) [Shard: user_shard_1] SELECT COUNT(*) FROM `users`
|
|
246
|
+
(0.3ms) [Shard: user_shard_2] SELECT COUNT(*) FROM `users`
|
|
247
|
+
(0.2ms) [Shard: user_shard_3] SELECT COUNT(*) FROM `users`
|
|
248
|
+
=> 1
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Sequencer
|
|
252
|
+
|
|
253
|
+
Sequencer provides generating global IDs.
|
|
254
|
+
|
|
255
|
+
Turntable has follow 2 sequencers currently:
|
|
256
|
+
|
|
257
|
+
* :mysql - Use database table to generate ids.
|
|
258
|
+
* :barrage - Use [barrage](https://github.com/drecom/barrage) gem to generate ids
|
|
259
|
+
|
|
260
|
+
### Mysql example
|
|
261
|
+
|
|
262
|
+
First, add configuration to turntable.yml and database.yml
|
|
263
|
+
|
|
264
|
+
* database.yml
|
|
265
|
+
|
|
266
|
+
```yaml
|
|
267
|
+
development:
|
|
268
|
+
...
|
|
269
|
+
seq: # <-- sequence database definition
|
|
270
|
+
user_seq_1:
|
|
271
|
+
<<: *spec
|
|
272
|
+
database: sample_app_user_seq_development
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
* turntable.yml
|
|
276
|
+
|
|
277
|
+
```yaml
|
|
278
|
+
development:
|
|
279
|
+
clusters:
|
|
280
|
+
user_cluster: # <-- cluster name
|
|
281
|
+
....
|
|
282
|
+
seq:
|
|
283
|
+
user_seq: # <-- sequencer name
|
|
284
|
+
seq_type: mysql # <-- sequencer type
|
|
285
|
+
connection: user_seq_1 # <-- sequencer database connection
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Add below to the migration:
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
create_sequence_for(:users) # <-- this line creates sequence table named `users_id_seq`
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Next, add sequencer definition to the model:
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
class User < ActiveRecord::Base
|
|
298
|
+
turntable :id
|
|
299
|
+
sequencer :users_seq # <-- this line enables sequencer module
|
|
300
|
+
has_one :status
|
|
301
|
+
end
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Barrage example
|
|
305
|
+
|
|
306
|
+
First, add barrage gem to your Gemfile:
|
|
307
|
+
|
|
308
|
+
```ruby
|
|
309
|
+
gem 'barrage'
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Then, add configuration to turntable.yml:
|
|
313
|
+
|
|
314
|
+
* turntable.yml
|
|
315
|
+
|
|
316
|
+
```yaml
|
|
317
|
+
development:
|
|
318
|
+
clusters:
|
|
319
|
+
user_cluster: # <-- cluster name
|
|
320
|
+
....
|
|
321
|
+
seq:
|
|
322
|
+
barrage_seq: # <-- sequencer name
|
|
323
|
+
seq_type: barrage # <-- sequencer type
|
|
324
|
+
options: # <-- options passed to barrage
|
|
325
|
+
generators:
|
|
326
|
+
- name: msec
|
|
327
|
+
length: 39 # MAX 17.4 years from start_at
|
|
328
|
+
start_at: 1396278000000 # 2014/04/01 00:00:00 JST
|
|
329
|
+
- name: redis_worker_id
|
|
330
|
+
length: 16
|
|
331
|
+
ttl: 300
|
|
332
|
+
redis:
|
|
333
|
+
host: '127.0.0.1'
|
|
334
|
+
- name: sequence
|
|
335
|
+
length: 9
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Next, add sequencer definition to the model:
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
class User < ActiveRecord::Base
|
|
342
|
+
turntable :id
|
|
343
|
+
sequencer :barrage_seq # <-- this line enables sequencer module
|
|
344
|
+
has_one :status
|
|
345
|
+
end
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Transactions
|
|
349
|
+
Turntable has some transaction support methods.
|
|
350
|
+
|
|
351
|
+
### shards_transaction
|
|
352
|
+
|
|
353
|
+
Pass AR::Base instances, `shards_transaction` method suitable shards
|
|
354
|
+
|
|
355
|
+
```ruby
|
|
356
|
+
user = User.find(2)
|
|
357
|
+
user3 = User.create(name: "hoge3")
|
|
358
|
+
|
|
359
|
+
User.shards_transaction([user, user3]) do
|
|
360
|
+
user.name = "hogehoge"
|
|
361
|
+
user3.name = "hogehoge3"
|
|
362
|
+
user.save!
|
|
363
|
+
user3.save!
|
|
364
|
+
end
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### cluster_transaction
|
|
368
|
+
|
|
369
|
+
transaction helper to execute transaction to all shards in the cluster:
|
|
370
|
+
|
|
371
|
+
```ruby
|
|
372
|
+
User.user_cluster_transaction do
|
|
373
|
+
# Transaction is opened all shards in "user_cluster"
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Migration
|
|
378
|
+
|
|
379
|
+
If you specify cluster or shard, migration will be executed to the cluster(or shard) and master database.
|
|
380
|
+
|
|
381
|
+
Default, migrations will be executed to all databases.
|
|
382
|
+
|
|
383
|
+
to specify cluster:
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
class CreateUsers < ActiveRecord::Migration
|
|
387
|
+
clusters :user_cluster
|
|
388
|
+
....
|
|
389
|
+
end
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
to specify shard:
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
class CreateUsers < ActiveRecord::Migration
|
|
396
|
+
shards :user_shard_01
|
|
397
|
+
....
|
|
398
|
+
end
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Limitations
|
|
402
|
+
|
|
403
|
+
* Queries includes "ORDER BY", "GROUP BY" and "LIMIT" clauses cannot be distributed.
|
|
404
|
+
* "has many through" and "habtm" relationships may causes wrong results. ex) `User-Friend-User` relation
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
## TIPS
|
|
408
|
+
|
|
409
|
+
### Send query to a specific shard.
|
|
410
|
+
|
|
411
|
+
Use `with_shard` method:
|
|
412
|
+
|
|
413
|
+
```ruby
|
|
414
|
+
AR::Base.connection.with_shard(shard1) do
|
|
415
|
+
# something queries to shard1
|
|
416
|
+
end
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
To access shard objects, use below:
|
|
420
|
+
|
|
421
|
+
* AR::Base.connection.shards # \\{shard_name => shard_obj,....}
|
|
422
|
+
* AR::Base#turntable_shard # Returns current object's shard
|
|
423
|
+
* AR::Base.connection.select_shard(shard_key_value) #=> shard
|
|
424
|
+
|
|
425
|
+
### Send query to all shards
|
|
426
|
+
|
|
427
|
+
Use with_all method:
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
User.connection.with_all do
|
|
431
|
+
User.order("created_at DESC").limit(3).all
|
|
432
|
+
end
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Connection Management
|
|
436
|
+
|
|
437
|
+
Rails's ConnectionManagement middleware keeps ActiveRecord's connection during the process is alive, but Turntable keeps more connections.
|
|
438
|
+
This may cause flooding max connections on your database. So, we made a middleware that disconnects on each request.
|
|
439
|
+
|
|
440
|
+
if you use turntable's ConnectionManagement middleware, add below line to your initializer.
|
|
441
|
+
|
|
442
|
+
```ruby
|
|
443
|
+
app.middleware.swap ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::Turntable::Rack::ConnectionManagement
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Performance Exception
|
|
447
|
+
|
|
448
|
+
To notice queries causing performance problem, Turntable has follow options.
|
|
449
|
+
|
|
450
|
+
* raise\_on\_not\_specified\_shard\_query - raises on queries execute on all shards
|
|
451
|
+
* raise\_on\_not\_specified\_shard\_update - raises on updates executed on all shards
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
Add to turntable.yml:
|
|
455
|
+
|
|
456
|
+
```yaml
|
|
457
|
+
development:
|
|
458
|
+
....
|
|
459
|
+
raise_on_not_specified_shard_query: true
|
|
460
|
+
raise_on_not_specified_shard_update: true
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## Thanks
|
|
464
|
+
|
|
465
|
+
ConnectionProxy, Distributed Migration implementation is inspired by Octopus and DataFabric.
|
|
466
|
+
|
|
467
|
+
## License
|
|
468
|
+
|
|
469
|
+
activerecord-turntable is released under the MIT license:
|
|
470
|
+
|
|
471
|
+
Copyright (c) 2012 Drecom Co.,Ltd.
|
|
472
|
+
|
|
473
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
474
|
+
a copy of this software and associated documentation files (the
|
|
475
|
+
"Software"), to deal in the Software without restriction, including
|
|
476
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
477
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
478
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
479
|
+
the following conditions:
|
|
480
|
+
|
|
481
|
+
The above copyright notice and this permission notice shall be
|
|
482
|
+
included in all copies or substantial portions of the Software.
|
|
483
|
+
|
|
484
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
485
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
486
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
487
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
488
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
489
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
490
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|