LoginSignup
111
97

More than 5 years have passed since last update.

Ridgepole を使ってみる

Last updated at Posted at 2014-12-20

RDBMS のスキーマ管理ツール Ridgepole を使ってみたのでメモ。

公式

Ridgepole
http://ridgepole.codenize.tools/

winebarrel/ridgepole
https://github.com/winebarrel/ridgepole

winebarrel/ridgepole-example
https://github.com/winebarrel/ridgepole-example

インストール

% gem install ridgepole mysql2 --no-rdoc --no-ri
% ridgepole -v       
ridgepole 0.6.4

使ってみる

テーブル定義などは ridgepole-example のものを使わせて頂いた。

作業用に適当なディレクトリを作成。

% mkdir ridgepole-example && cd ridgepole-example

データベースを2つ作成。

% mysql -u root -e "CREATE DATABASE IF NOT EXISTS blog DEFAULT CHARACTER SET utf8;"
% mysql -u root -e "CREATE DATABASE IF NOT EXISTS blog_prod DEFAULT CHARACTER SET utf8;"

Schemafile を作成。

% cat << _EOF_ > Schemafile 
create_table "articles", force: true do |t|
  t.string   "title"
  t.text     "text"
  t.datetime "created_at"
  t.datetime "updated_at"
end

create_table "comments", force: true do |t|
  t.string   "commenter"
  t.text     "body"
  t.integer  "article_id"
  t.datetime "created_at"
  t.datetime "updated_at"
end

add_index "comments", ["article_id"], name: "index_comments_on_article_id", using: :btree
_EOF_

ドライラン実行。
DB 接続設定は引数でも指定できるようなのでここでは引数を使ってみた。

% ridgepole -c '{adapter: mysql2, socket: /tmp/mysql.sock, database: blog}' --apply --dry-run -f Schemafile
Apply `Schemafile` (dry-run)
create_table("articles", {}) do |t|
  t.string("title", {:limit=>255})
  t.text("text", {:limit=>65535})
  t.datetime("created_at", {})
  t.datetime("updated_at", {})
end

create_table("comments", {}) do |t|
  t.string("commenter", {:limit=>255})
  t.text("body", {:limit=>65535})
  t.integer("article_id", {:limit=>4})
  t.datetime("created_at", {})
  t.datetime("updated_at", {})
end
add_index("comments", ["article_id"], {:name=>"index_comments_on_article_id", :using=>:btree})

# CREATE TABLE `articles` (
# `id` int(11) auto_increment PRIMARY KEY,
# `title` varchar(255),
# `text` text,
# `created_at` datetime,
# `updated_at` datetime)
# ENGINE=InnoDB
# CREATE TABLE `comments` (
# `id` int(11) auto_increment PRIMARY KEY,
# `commenter` varchar(255),
# `body` text,
# `article_id` int(11),
# `created_at` datetime,
# `updated_at` datetime)
# ENGINE=InnoDB
# CREATE  INDEX `index_comments_on_article_id` USING btree ON `comments` (
# `article_id`)

当然DB接続設定はファイルに書く事も出来るので、ここでファイルを作成しとく。

% cat << _EOF_ > database.yml
development:
  adapter: mysql2
  encoding: utf8
  socket: /tmp/mysql.sock
  database: blog
  username: root
production:
  adapter: mysql2
  encoding: utf8
  socket: /tmp/mysql.sock
  database: blog_prod
  username: root
_EOF_

Rails の database.yml と同じ形式なのでそのまま使う事も可能。

実際に DB に適用してみる。
ここでは development 環境の DB にのみ適用したいので -E development を指定。

% ridgepole -c database.yml -E development --apply -f Schemafile
Apply `Schemafile`
-- create_table("articles", {})
   -> 0.1952s
-- create_table("comments", {})
   -> 0.0540s
-- add_index("comments", ["article_id"], {:name=>"index_comments_on_article_id", :using=>:btree})
   -> 0.0734s

テーブルが作成された事を確認。

% mysql -uroot blog -e "SHOW TABLES; SHOW CREATE TABLE articles\G SHOW CREATE TABLE comments\G"
+----------------+
| Tables_in_blog |
+----------------+
| articles       |
| comments       |
+----------------+
*************************** 1. row ***************************
       Table: articles
Create Table: CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `text` text,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
*************************** 1. row ***************************
       Table: comments
Create Table: CREATE TABLE `comments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commenter` varchar(255) DEFAULT NULL,
  `body` text,
  `article_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_comments_on_article_id` (`article_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8

同様にして production にも適用しておく。

% ridgepole -c database.yml -E production --apply -f Schemafile

テーブル定義のエクスポート

既に存在する DB 上のテーブル定義を Schemafile にエクスポートする事も出来る。

まずは先ほど作成した development 用 DB に適当にカラムを追加してみる。

% mysql -uroot blog -e 'ALTER TABLE `articles` ADD `category_id` INT DEFAULT NULL AFTER `text`; SHOW CREATE TABLE articles\G'
*************************** 1. row ***************************
       Table: articles
Create Table: CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `text` text,
  `category_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

テーブル定義をエクスポート。

% mkdir schemas
% ridgepole -c database.yml -E development --export --output schemas/Schemafile 
Export Schema to `schemas/Schemafile`

出力された Schemafile を確認。
追加したカラムも反映されている。

% cat schemas/Schemafile 
create_table "articles", force: :cascade do |t|
  t.string   "title",       limit: 255
  t.text     "text",        limit: 65535
  t.integer  "category_id", limit: 4
  t.datetime "created_at"
  t.datetime "updated_at"
end

create_table "comments", force: :cascade do |t|
  t.string   "commenter",  limit: 255
  t.text     "body",       limit: 65535
  t.integer  "article_id", limit: 4
  t.datetime "created_at"
  t.datetime "updated_at"
end

add_index "comments", ["article_id"], name: "index_comments_on_article_id", using: :btree

テーブル毎にスキーマファイルを分割したい場合は --split オプションを指定する。

% ridgepole -c database.yml -E development --export --split --output schemas/Schemafile
Export Schema
  write `schemas/articles.schema`
  write `schemas/comments.schema`
  write `schemas/Schemafile`

この機能があるので、ローカルでは直接 DB を変更して開発し、定義が固まったらエクスポートしてコミット、という方法も取れる。
DSL に慣れない場合は当面この方法でも良さそう。

テーブル定義変更

今度は Schemafile 側を変更してみる。

% sed -i '/"text".*$/a \  t.text     "author",      limit: 65535' schemas/articles.schema
% cat schemas/articles.schema
create_table "articles", force: :cascade do |t|
  t.string   "title",       limit: 255
  t.text     "text",        limit: 65535
  t.text     "author",      limit: 65535
  t.integer  "category_id", limit: 4
  t.datetime "created_at"
  t.datetime "updated_at"
end

--apply --dry-run してみて発行される ALTER 文を確認。

% ridgepole -c database.yml -E development --apply --dry-run -f schemas/Schemafile
Apply `schemas/Schemafile` (dry-run)
add_column("articles", "author", :text, {:limit=>65535, :after=>"text"})

# ALTER TABLE `articles` ADD `author` text AFTER `text`

DB との差分を確認

現在の DB 上の定義との差分も確認出来る。
ただ、 Schemafile を分割している場合は全てカレントディレクトリに存在していないといけないっぽい。
コメント欄で教えて頂いた通り、バージョン 0.5.1 で分割された Schemafile でも --diff が使えるようになった。

% ridgepole -E development --diff database.yml schemas/Schemafile
add_column("articles", "author", :text, {:limit=>65535, :after=>"text"})

# ALTER TABLE `articles` ADD `author` text AFTER `text`

development 環境を Schemafile にダンプしてあるので、-E production を指定すれば production 環境との差分も確認可能。

% ridgepole -E production --diff database.yml schemas/Schemafile
add_column("articles", "author", :text, {:limit=>65535, :after=>"text"})
add_column("articles", "category_id", :integer, {:limit=>4, :after=>"author"})

# ALTER TABLE `articles` ADD `author` text AFTER `text`
# ALTER TABLE `articles` ADD `category_id` int(11) AFTER `author`

また、 DB 接続設定ファイルを別ファイルに分ければエクスポートを経由しなくても DB 同士の差分を比較できた。

development.yml
adapter: mysql2
encoding: utf8
socket: /tmp/mysql.sock
database: blog
username: root
production.yml
adapter: mysql2
encoding: utf8
socket: /tmp/mysql.sock
database: blog_prod
username: root
% ridgepole --diff production.yml development.yml
add_column("articles", "category_id", :integer, {:limit=>4, :after=>"text"})

# ALTER TABLE `articles` ADD `category_id` int(11) AFTER `text`

--diff オプションの引数は current expected の順だが、 --reverse を指定すると逆向きの DDL を出力してくれる。

% ridgepole -E production --diff database.yml schemas/Schemafile --reverse
remove_column("articles", "author")
remove_column("articles", "category_id")

# ALTER TABLE `articles` DROP `author`
# ALTER TABLE `articles` DROP `category_id`

パーティション

SHOW CREATE TABLE した時にコメントで出力されるパーティションをエクスポート出来るようにするには別途 activerecord-mysql-awesome gem が必要な模様。
インストール。

% gem install activerecord-mysql-awesome
Fetching: activerecord-mysql-awesome-0.0.8.gem (100%)
Successfully installed activerecord-mysql-awesome-0.0.8
1 gem installed

適当にパーティションを追加。

% mysql -uroot blog -e 'ALTER TABLE `articles` PARTITION BY HASH(id) PARTITIONS 8; SHOW CREATE TABLE articles\G'
*************************** 1. row ***************************
       Table: articles
Create Table: CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `text` text,
  `category_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY HASH (id)
PARTITIONS 8 */

再度スキーマをエクスポート。
activerecord-mysql-awesome を有効にするための --enable-mysql-awesome オプションを指定。

% ridgepole -c database.yml -E development --enable-mysql-awesome --export --split --output schemas/Schemafile                                            
Export Schema
  write `schemas/articles.schema`
  write `schemas/comments.schema`
  write `schemas/Schemafile`

以下のようにパーティションが出力されている事を確認。

% cat schemas/articles.schema                                                                          
create_table "articles", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8\n/*!50100 PARTITION BY HASH (id)\nPARTITIONS 8 */" do |t|
  t.string   "title",       limit: 255
  t.text     "text",        limit: 65535
  t.integer  "category_id", limit: 4
  t.datetime "created_at"
  t.datetime "updated_at"
end

試しに --mysql-use-alter オプションも併せて指定してみたが、PARTITION 作成文は ALTER 文としては出力されない模様。

このスキーマを production 用 DB に適用してみる。
create_tagble で出力されているので、一旦 articlesDROP TABLE してから実行。

% mysql -uroot blog_prod -e 'DROP TABLE `articles`;'
% ridgepole -c database.yml -E production --merge -f schemas/Schemafile 
Merge `schemas/Schemafile`
[WARNING] No difference of schema configuration for table `comments`. (if you changed some options, please reconfirm your Schemafile)
-- create_table("articles", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8\n/*!50100 PARTITION BY HASH (id)\nPARTITIONS 8 */"})
   -> 0.1177s

確認。

% mysql -uroot blog_prod -e 'SHOW CREATE TABLE `articles`\G'
*************************** 1. row ***************************
       Table: articles
Create Table: CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `text` text,
  `category_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY HASH (id)
PARTITIONS 8 */

ちゃんとパーティションも作成されたっぽい。

unsigned 型

category_id の型を unsigned int に変更。

% mysql -uroot blog -e 'ALTER TABLE `articles` MODIFY `category_id` INT unsigned DEFAULT NULL; SHOW CREATE TABLE `articles`\G'
*************************** 1. row ***************************
       Table: articles
Create Table: CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `text` text,
  `category_id` int(10) unsigned DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY HASH (id)
PARTITIONS 8 */

articles テーブルのスキーマをエクスポート。
--tables オプションに続いてテーブル名を指定すると該当のテーブルのみエクスポートされる。
が、その場合親の Schemafile も指定したテーブルのスキーマファイルのみが require されるように書き換わってしまうので注意。

また、 activerecord-mysql-awesome を有効にするための --enable-mysql-awesome オプションが無いと unsigned が出力されない模様。

# --enable-mysql-awesome 無し
% ridgepole -c database.yml -E development --tables articles --export --split --output schemas/Schemafile
# unsigned の情報が消えてしまっている
% cat schemas/articles.schema 
create_table "articles", force: :cascade do |t|
  t.string   "title",       limit: 255
  t.text     "text",        limit: 65535
  t.integer  "category_id", limit: 4
  t.datetime "created_at"
  t.datetime "updated_at"
end

# --enable-mysql-awesome 有り
% ridgepole -c database.yml -E development --enable-mysql-awesome --tables articles --export --split --output schemas/Schemafile

# unsigned: true が出力された
% cat schemas/articles.schema                                                                                                   
create_table "articles", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8\n/*!50100 PARTITION BY HASH (id)\nPARTITIONS 8 */" do |t|
  t.string   "title",       limit: 255
  t.text     "text",        limit: 65535
  t.integer  "category_id", limit: 4,     unsigned: true
  t.datetime "created_at"
  t.datetime "updated_at"
end

他の DB に反映させるには --apply 時にも --enable-mysql-awesome オプションの指定が必要なので注意する。

外部キー制約

外部キー制約を追加してみる。

手元で使っていた 5.6 ではパーティションが存在すると外部キー制約を作成できないようなので一旦 articlesDROP して作り直し。

% mysql -uroot blog -e 'DROP TABLE `articles`'
% ridgepole -c database.yml -E development --tables articles --apply -f schemas/Schemafile
% mysql -uroot blog -e 'SHOW CREATE TABLE `articles`\G'
*************************** 1. row ***************************
       Table: articles
Create Table: CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `text` text,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

外部キー制約を作成。

% mysql -uroot blog -e 'ALTER TABLE `comments` MODIFY `article_id` INT NOT NULL;'
% mysql -uroot blog -e 'ALTER TABLE `comments` ADD CONSTRAINT `fk_article_id_on_comments` FOREIGN KEY (`article_id`) REFERENCES `articles` (`ID`); SHOW CREATE TABLE `comments`\G'
*************************** 1. row ***************************
       Table: comments
Create Table: CREATE TABLE `comments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commenter` varchar(255) DEFAULT NULL,
  `body` text,
  `article_id` int(11) NOT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_comments_on_article_id` (`article_id`) USING BTREE,
  CONSTRAINT `fk_article_id_on_comments` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

作成したテーブルからスキーマファイルをエクスポート。

% ridgepole -c database.yml -E development --export --split --output schemas/Schemafile
% cat schemas/comments.schema
create_table "comments", force: :cascade do |t|
  t.string   "commenter",  limit: 255
  t.text     "body",       limit: 65535
  t.integer  "article_id", limit: 4,     null: false
  t.datetime "created_at"
  t.datetime "updated_at"
end

add_index "comments", ["article_id"], name: "index_comments_on_article_id", using: :btree

add_foreign_key "comments", "articles", name: "fk_article_id_on_comments"

ちゃんと外部キー制約もエクスポートされた。

一旦 production を DROP して適用してみる。

% mysql -uroot blog_prod -e 'DROP TABLE `comments`, `articles`'
% ridgepole -c database.yml -E production --apply -f schemas/Schemafile
Apply `schemas/Schemafile`
-- create_table("articles", {})
   -> 0.0093s
-- create_table("comments", {})
   -> 0.0073s
-- add_index("comments", ["article_id"], {:name=>"index_comments_on_article_id", :using=>:btree})
   -> 0.0142s
-- add_foreign_key("comments", "articles", {:name=>"fk_article_id_on_comments"})
   -> 0.0157s

適用されたっぽい。
確認。

% mysql -uroot blog_prod -e 'SHOW CREATE TABLE `comments`\G'
*************************** 1. row ***************************
       Table: comments
Create Table: CREATE TABLE `comments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commenter` varchar(255) DEFAULT NULL,
  `body` text,
  `article_id` int(11) NOT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_comments_on_article_id` (`article_id`) USING BTREE,
  CONSTRAINT `fk_article_id_on_comments` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

外部キー制約が作成された。

感想

Rails の Migration に慣れているなら standalone-migrations もあるけど、Ridgepole なら DB を直接弄った場合でもちゃんと管理出来る。
開発中にちょっとした変更でいちいち Migration 弄るのは面倒くさくて好きじゃないのでこれは結構嬉しいかも。
DSL も違和感ないので今 Migration 使ってる場合でも移行するのは簡単そう。

参考

クックパッドにおける最近のActiveRecord運用事情 - クックパッド開発者ブログ
http://techlife.cookpad.com/entry/2014/08/28/194147

Ridgepole を PostgreSQL と組み合わせて使う - Qiita
http://qiita.com/Tomohiro/items/89846cc2cfdf53ac993e

111
97
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
111
97