- 更新日: 2017年7月18日
- Rails
RailsでMySQLパーティショニングのマイグレーション
Rails で MySQL パーティショニングを使いたかったのですが、マイグレーションを書くのにちょっとコツが必要だったので備忘録メモです。MySQL のパーティショニングを使う場合、通常のテーブルとは違って、色々と制約や制限があります。それらの制約を守ったり、制限を回避しつつマイグレートを書かなければなりません。詳しくは以下。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 19.6 パーティショニングの制約と制限
— 環境 —
Rails 5.1
ActiveRecord 5.1
MySQL 5.6
MySQL パーティショニング利用での制約と制限
MySQL の公式ドキュメントに詳しく書いてありますが、DBの主キーやユニークキーについて、以下のルールを守る必要があります。
パーティション化されたテーブルのパーティショニング式で使用されるすべてのカラムは、テーブルが持つことができるすべての一意キーの一部である必要があります。つまり、テーブルのすべての一意キーは、テーブルのパーティショニング式内のすべてのカラムを使用する必要があります(これには、テーブルの主キーも含まれます (自明で一意キーであるため)。
他にも、外部キーが使えないなど色々な制約・制限がありますので注意が必要です。
とりあえずパーティショニング作成における、主キーとユニークキーのルールを守るために、マイグレーションで以下のアプローチを取りました。
1. 一旦、デフォルトのプライマリーキーである id カラムを作成しないようにする。
2. プライマリキーと AUTO_INCREMENT 設定のない id カラムを明示的に作成。
3. テーブルを生成。
4. ユニークインデックス追加(add_index)には、パーティショニングに使うカラムを含めて複合ユニークにする。
5. ALTER TABLE で、id カラムとパーティショニングに使うカラムで複合プライマリキーを追加。
6. ALTER TABLE で、id カラムに AUTO_INCREMENT を追加。
7. ALTER TABLE で、パーティションを追加。
MySQL パーティション設定のマイグレーション
articles テーブルにおいて、posted_at カラムでパーティショニングを行う場合の例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class CreateArticles < ActiveRecord::Migration[5.1] def change # 'id: false' でデフォルトの id カラムを作らない create_table :articles, id: false do |t| t.references :user, null: false # 明示的に id カラムを作成、この id カラムにはプライマリーキー/AUTO_INCREMENTの設定がない状態。 t.integer :id, null: false, limit: 8 # bigint t.string :title, null: false t.text :content, null: false t.datetime :posted_at, null: false t.timestamps end # content カラムとパーティショニングに使う posted_at カラムで複合ユニークキーを追加 add_index :articles, [:content, :posted_at], unique: true add_index :articles, :posted_at # id カラムとパーティショニングに使う posted_at カラムで複合プライマリーキーを追加 execute 'ALTER TABLE articles ADD PRIMARY KEY(id, posted_at);' # id カラムに AUTO_INCREMENT を追加 execute 'ALTER TABLE articles MODIFY id BIGINT(20) NOT NULL AUTO_INCREMENT;' # 30年分(360ヶ月分)のパーティションを追加 execute( 'ALTER TABLE articles PARTITION BY RANGE COLUMNS(posted_at) (' + ('2017'..'2046').to_a.map { |year| ('01'..'12').to_a.map { |month| "PARTITION p#{year}#{month} VALUES LESS THAN ('#{year}-#{month}-01 00:00:00') COMMENT = '#{year}-#{month}' ENGINE = InnoDB" } }.flatten.join(', ') + ');' ) end end |
「create_table :articles, id: false」で、ActiveRecord デフォルトの id カラムが生成されなくなる。なので、ブロック内で明示的に id カラムを作成しています。この明示的に追加した id カラムには、プライマリキーと AUTO_INCREMENT の設定がない状態です。パーティショニングの制約・制限を守るために、後で ALTER TABLE でプライマリキーと AUTO_INCREMENT を追加するようにしました。
また、重複投稿がないように、content カラムにユニークキーを張りたいのだけど、単独でするとエラーになってしまいます。パーティショニングを利用する場合、ユニークキーにパーティショニングに使うカラムを含める必要があるためです。なので、posted_at カラムを含めて複合ユニークを作成しました。
「add partitions for 30 years」の30年分(360ヶ月分)のパーティションを作成する部分は、実際には以下のような SQL を発行します。1行ずつ書くのはしんどいので Ruby コードでクエリを組み立てました。
1 2 3 4 5 6 7 |
# ALTER TABLE articles PARTITION BY RANGE COLUMNS(posted_at) ( # PARTITION p201701 VALUES LESS THAN ('2017-01-01 00:00:00') COMMENT = '2017-01' ENGINE = InnoDB, # PARTITION p201702 VALUES LESS THAN ('2017-02-01 00:00:00') COMMENT = '2017-02' ENGINE = InnoDB, # PARTITION p201703 VALUES LESS THAN ('2017-03-01 00:00:00') COMMENT = '2017-03' ENGINE = InnoDB, # ... # PARTITION p204612 VALUES LESS THAN ('2046-12-01 00:00:00') COMMENT = '2046-12' ENGINE = InnoDB, # ); |
このマイグレーション実行で、30年分のパーティションを持つ articles テーブルが作成されます。
Article モデルの設定
ActiveRecord のモデル側(Article モデル)では、プライマリキーは :id だけで良いので以下のように書きます。
1 2 3 4 5 6 7 |
class Article < ApplicationRecord belongs_to :user # migration sets composite primary keys on :id, posted_at to db, # but article primary key should be only :id in ActiveRecord model. self.primary_key = :id end |
この設定を書かないと、Sidekiq などで「ActiveRecord は composite primary keys をサポートしてないよ」的なエラーが出た記憶があります。
パーティショニングされたレコードの確認
posted_at カラムを適当に設定して、いくつかレコードを保存した後、以下の SQL 発行で確認。
1 2 3 4 5 6 7 8 9 10 11 |
> select TABLE_SCHEMA, TABLE_NAME, PARTITION_NAME, PARTITION_ORDINAL_POSITION, TABLE_ROWS from INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='articles'; +--------------------+------------+----------------+----------------------------+------------+ | TABLE_SCHEMA | TABLE_NAME | PARTITION_NAME | PARTITION_ORDINAL_POSITION | TABLE_ROWS | +--------------------+------------+----------------+----------------------------+------------+ | myapp_development | statuses | p201701 | 1 | 5 | | myapp_development | statuses | p201702 | 2 | 2 | | myapp_development | statuses | p201703 | 3 | 3 | ... |
レコードがパーティションに分散されて保存されていることが確認できました。
- – 参考リンク –
- Rails3からMySQLのパーティショニングを扱う時の注意点 – Qiita
- Rails + mysql でテーブルのidのauto incrementをやめる – Qiita
- 今さらだけどMySQLのパーティショニング機能を試してみた – (゚∀゚)o彡 sasata299’s blog
- 検証:パーティショニングテーブルの挙動 – Qiita
- MySQLパーティショニングの設定、追加、削除、再構成 – Qiita
- Rails の関連記事
- Rails ActiveRecordでdatetime型カラムのGROUP BY集計にタイムゾーンを考慮する
- RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方
- RailsでAMPに対応するgemをリリースしました
- Railsでrequest.urlとrequest.original_urlの違い
- Railsでwheneverによるcronバッチ処理
- Google AnalyticsのRails Turbolinks対応
- Railsアプリにソーシャル・シェアボタンを簡単設置
- Rails監視ツール用にErrbitをHerokuで運用
- Facebook APIバージョンのアップグレード手順(Rails OmniAuth)
- window.NREUMがHTMLヘッダー部に自動挿入されるのはNew Relic用
Leave Your Message!