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.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.travis.yml +3 -5
  4. data/CHANGELOG.md +70 -0
  5. data/Guardfile +7 -5
  6. data/README.md +490 -0
  7. data/Rakefile +37 -22
  8. data/activerecord-turntable.gemspec +37 -34
  9. data/gemfiles/rails4_0.gemfile +6 -0
  10. data/gemfiles/rails4_1.gemfile +6 -0
  11. data/lib/active_record/turntable/active_record_ext/abstract_adapter.rb +14 -29
  12. data/lib/active_record/turntable/active_record_ext/activerecord_import_ext.rb +45 -0
  13. data/lib/active_record/turntable/active_record_ext/acts_as_archive_extension.rb +21 -0
  14. data/lib/active_record/turntable/active_record_ext/association.rb +85 -0
  15. data/lib/active_record/turntable/active_record_ext/association_preloader.rb +37 -0
  16. data/lib/active_record/turntable/active_record_ext/clever_load.rb +33 -76
  17. data/lib/active_record/turntable/active_record_ext/connection_handler_extension.rb +31 -0
  18. data/lib/active_record/turntable/active_record_ext/database_tasks.rb +81 -0
  19. data/lib/active_record/turntable/active_record_ext/fixtures.rb +54 -42
  20. data/lib/active_record/turntable/active_record_ext/locking_optimistic.rb +101 -0
  21. data/lib/active_record/turntable/active_record_ext/log_subscriber.rb +28 -46
  22. data/lib/active_record/turntable/active_record_ext/migration_proxy.rb +7 -0
  23. data/lib/active_record/turntable/active_record_ext/persistence.rb +96 -94
  24. data/lib/active_record/turntable/active_record_ext/relation.rb +31 -0
  25. data/lib/active_record/turntable/active_record_ext/schema_dumper.rb +18 -28
  26. data/lib/active_record/turntable/active_record_ext/transactions.rb +9 -3
  27. data/lib/active_record/turntable/active_record_ext.rb +26 -11
  28. data/lib/active_record/turntable/algorithm/base.rb +1 -1
  29. data/lib/active_record/turntable/algorithm/range_bsearch_algorithm.rb +1 -1
  30. data/lib/active_record/turntable/algorithm.rb +7 -3
  31. data/lib/active_record/turntable/base.rb +67 -14
  32. data/lib/active_record/turntable/cluster.rb +46 -2
  33. data/lib/active_record/turntable/config.rb +1 -1
  34. data/lib/active_record/turntable/connection_proxy/mixable.rb +7 -29
  35. data/lib/active_record/turntable/connection_proxy.rb +61 -72
  36. data/lib/active_record/turntable/error.rb +5 -6
  37. data/lib/active_record/turntable/helpers.rb +5 -1
  38. data/lib/active_record/turntable/migration.rb +9 -49
  39. data/lib/active_record/turntable/mixer/fader/calculate_shards_sum_result.rb +13 -2
  40. data/lib/active_record/turntable/mixer/fader/select_shards_merge_result.rb +17 -6
  41. data/lib/active_record/turntable/mixer/fader/specified_shard.rb +3 -1
  42. data/lib/active_record/turntable/mixer/fader.rb +12 -10
  43. data/lib/active_record/turntable/mixer.rb +59 -29
  44. data/lib/active_record/turntable/plugin.rb +6 -0
  45. data/lib/active_record/turntable/pool_proxy.rb +12 -19
  46. data/lib/active_record/turntable/rack/query_cache.rb +20 -23
  47. data/lib/active_record/turntable/rack.rb +4 -2
  48. data/lib/active_record/turntable/railtie.rb +4 -3
  49. data/lib/active_record/turntable/railties/databases.rake +81 -122
  50. data/lib/active_record/turntable/seq_shard.rb +1 -1
  51. data/lib/active_record/turntable/sequencer/api.rb +1 -1
  52. data/lib/active_record/turntable/sequencer/barrage.rb +28 -0
  53. data/lib/active_record/turntable/sequencer.rb +27 -9
  54. data/lib/active_record/turntable/shard.rb +2 -2
  55. data/lib/active_record/turntable/sql_tree_patch.rb +1 -1
  56. data/lib/active_record/turntable/version.rb +1 -1
  57. data/lib/active_record/turntable.rb +26 -16
  58. data/lib/generators/templates/turntable.yml +9 -7
  59. data/spec/active_record/turntable/active_record_ext/association_preloader_spec.rb +78 -0
  60. data/spec/active_record/turntable/active_record_ext/association_spec.rb +72 -0
  61. data/spec/active_record/turntable/active_record_ext/clever_load_spec.rb +25 -46
  62. data/spec/active_record/turntable/active_record_ext/locking_optimistic_spec.rb +28 -0
  63. data/spec/active_record/turntable/active_record_ext/persistence_spec.rb +46 -25
  64. data/spec/active_record/turntable/algorithm/range_algorithm_spec.rb +4 -4
  65. data/spec/active_record/turntable/algorithm/range_bsearch_algorithm_spec.rb +35 -0
  66. data/spec/active_record/turntable/algorithm_spec.rb +28 -12
  67. data/spec/active_record/turntable/base_spec.rb +1 -1
  68. data/spec/active_record/turntable/cluster_spec.rb +27 -5
  69. data/spec/active_record/turntable/config_spec.rb +2 -2
  70. data/spec/active_record/turntable/connection_proxy_spec.rb +112 -45
  71. data/spec/active_record/turntable/finder_spec.rb +24 -11
  72. data/spec/active_record/turntable/mixer_spec.rb +21 -21
  73. data/spec/active_record/turntable/rack/query_cache_spec.rb +19 -0
  74. data/spec/active_record/turntable/sequencer/api_spec.rb +38 -0
  75. data/spec/active_record/turntable/sequencer/barrage_spec.rb +22 -0
  76. data/spec/active_record/turntable/sequencer/mysql_spec.rb +22 -0
  77. data/spec/active_record/turntable/shard_spec.rb +1 -1
  78. data/spec/active_record/turntable/transaction_spec.rb +35 -0
  79. data/spec/active_record/turntable_spec.rb +4 -4
  80. data/spec/config/database.yml +24 -34
  81. data/spec/config/turntable.yml +18 -1
  82. data/spec/fabricators/turntable_fabricator.rb +0 -2
  83. data/spec/models/card.rb +3 -0
  84. data/spec/models/cards_user.rb +10 -0
  85. data/spec/models/cards_users_histories.rb +7 -0
  86. data/spec/models/events_users_history.rb +7 -0
  87. data/spec/models/user.rb +7 -0
  88. data/spec/models/user_status.rb +6 -0
  89. data/spec/spec_helper.rb +10 -4
  90. data/spec/support/matchers/be_saved_to.rb +6 -0
  91. data/spec/support/turntable_helper.rb +29 -0
  92. metadata +124 -74
  93. data/README.rdoc +0 -294
  94. data/gemfiles/rails3_0.gemfile +0 -7
  95. data/gemfiles/rails3_1.gemfile +0 -6
  96. data/gemfiles/rails3_2.gemfile +0 -6
  97. data/lib/active_record/turntable/compatible.rb +0 -19
  98. data/sample_app/.gitignore +0 -16
  99. data/sample_app/Gemfile +0 -41
  100. data/sample_app/README.rdoc +0 -261
  101. data/sample_app/Rakefile +0 -7
  102. data/sample_app/app/assets/images/rails.png +0 -0
  103. data/sample_app/app/assets/javascripts/application.js +0 -15
  104. data/sample_app/app/assets/stylesheets/application.css +0 -13
  105. data/sample_app/app/controllers/application_controller.rb +0 -3
  106. data/sample_app/app/helpers/application_helper.rb +0 -2
  107. data/sample_app/app/mailers/.gitkeep +0 -0
  108. data/sample_app/app/models/.gitkeep +0 -0
  109. data/sample_app/app/models/user.rb +0 -4
  110. data/sample_app/app/views/layouts/application.html.erb +0 -14
  111. data/sample_app/config/application.rb +0 -65
  112. data/sample_app/config/boot.rb +0 -6
  113. data/sample_app/config/database.yml +0 -70
  114. data/sample_app/config/environment.rb +0 -5
  115. data/sample_app/config/environments/development.rb +0 -37
  116. data/sample_app/config/environments/production.rb +0 -67
  117. data/sample_app/config/environments/test.rb +0 -37
  118. data/sample_app/config/initializers/backtrace_silencers.rb +0 -7
  119. data/sample_app/config/initializers/inflections.rb +0 -15
  120. data/sample_app/config/initializers/mime_types.rb +0 -5
  121. data/sample_app/config/initializers/secret_token.rb +0 -7
  122. data/sample_app/config/initializers/session_store.rb +0 -8
  123. data/sample_app/config/initializers/wrap_parameters.rb +0 -14
  124. data/sample_app/config/locales/en.yml +0 -5
  125. data/sample_app/config/routes.rb +0 -58
  126. data/sample_app/config/turntable.yml +0 -64
  127. data/sample_app/config.ru +0 -4
  128. data/sample_app/db/migrate/20120316073058_create_users.rb +0 -11
  129. data/sample_app/db/seeds.rb +0 -7
  130. data/sample_app/lib/assets/.gitkeep +0 -0
  131. data/sample_app/lib/tasks/.gitkeep +0 -0
  132. data/sample_app/log/.gitkeep +0 -0
  133. data/sample_app/public/404.html +0 -26
  134. data/sample_app/public/422.html +0 -26
  135. data/sample_app/public/500.html +0 -25
  136. data/sample_app/public/favicon.ico +0 -0
  137. data/sample_app/public/index.html +0 -241
  138. data/sample_app/public/robots.txt +0 -5
  139. data/sample_app/script/rails +0 -6
  140. data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
  141. data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
  142. data/sample_app/vendor/plugins/.gitkeep +0 -0
  143. data/spec/test_models.rb +0 -27
  144. data/spec/turntable_helper.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2cf165fcac886a12b91e1c524f6e39909d771396
4
- data.tar.gz: c86ffe9b2f0a5e10718483199aa1aa9fbf7453f6
3
+ metadata.gz: 2d2ed24b4fc25e1ec4752797494ce5f7e01aba00
4
+ data.tar.gz: e9686336be4cac05c60c3d129d3b94c3d562674c
5
5
  SHA512:
6
- metadata.gz: 4f0f21e8874756dd0e7638759d1517a472a96a40fa7cadafe96399078a20598a7b5880b73f1da283784914bcdcbffbca839663693426a4aa039abb26ee4f2bb2
7
- data.tar.gz: 0e4c417bf23c6a4baf9a64fb0b72e175b44debfb9a9c7939cbf04cf1e04409e43383ee5e30a4b3f90235ccd329b172182667dd005c6fab66c57782d30d7d6213
6
+ metadata.gz: 948dc1c9c05cedd33df32a763b79a587ec7e748ef03e96b8b763b7e991716b82f619c2bfbb4e4980136ae507c78dda9e06b733768613ecca2468ce8bd33042f3
7
+ data.tar.gz: 887ec36800641c5f632acdc83cc8abcb22f428ee196743e1d64b8b371d93e99fedc776d4c4151b91b57fc8586da80f91692f75c5249a354b076a90060e423f6e
data/.gitignore CHANGED
@@ -26,3 +26,8 @@ TAGS
26
26
 
27
27
  # For rubinius:
28
28
  #*.rbc
29
+
30
+ # gtags
31
+ GTAGS
32
+ GRTAGS
33
+ GPATH
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/rails3_0.gemfile
8
- - gemfiles/rails3_1.gemfile
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', :version => 2 do
5
- watch(%r{^spec/.+_spec\.rb$})
6
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec" }
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
+ [![Gem Version](https://badge.fury.io/rb/activerecord-turntable.svg)](http://badge.fury.io/rb/activerecord-turntable)
4
+ [![Build Status](https://travis-ci.org/drecom/activerecord-turntable.svg?branch=master)](https://travis-ci.org/drecom/activerecord-turntable)
5
+ [![Dependency Status](https://gemnasium.com/drecom/activerecord-turntable.svg)](https://gemnasium.com/drecom/activerecord-turntable)
6
+ [![Coverage Status](https://coveralls.io/repos/drecom/activerecord-turntable/badge.png?branch=master)](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.