SlideShare a Scribd company logo
PostgreSQLカンファレンス2013 LightningTalk

DBスキーマも
バージョン管理したい!
—省略してない版—

Makoto Kuwata <kwa@kuwata-lab.com>
http://www.kuwata-lab.com/
更新履歴:
・2013-11-13 migr8.rbの設定における若干の間違いを修正
・2013-11-14 SQLite3での設定等を修正、「migr8.rb new --table=users」を追加
背景
✓ ソースコードのバージョン管理は普及してる
Git、Mercurial、Subversion、…

✓ 対して、DBスキーマのバージョン管理は…
そもそも存在を知らない人が多い
OT: 控え室にて
Q: PostgreSQL界隈では、
スキーマのバージョン管理
では何が人気なんですか?

A: そんなものはない

達人のお墨付き!!
(石井さんごめんなさい)
発表のゴール
✓ DBスキーマもバージョン管理できることを
知ってもらう
・サンプルその1:Ruby on Rails

(DSL派)

・サンプルその2:Migr8.rb    (SQL派)

✓ バージョン管理ツールの選択ポイントを紹介
・DSL vs. SQL 
・バージョン番号の採番方法
用語解説:「マイグレーション」
✓ DBMS自体を変更すること
例:OracleからPostgreSQLにマイグレーションした

✓ DBMSのバージョンを更新すること
例:PosrgreSQLを8.4から9.3にマイグレーションした

✓ DBスキーマのバージョンを更新すること
例:テーブル追加したのでマイグレーションを実行してね
ここではこの意味で使う
はじめてのDBスキーマ管理

Ruby on Rails 編(DSL派)
Ruby on Rails とは?
✓ Ruby製の超人気フレームワーク
http://rubyonrails.org/

✓ マイグレーション機能をいち早く搭載
他のフレームワークがこぞって真似することに
→ Railsのやり方を知れば、他もだいたい同じ
マイグレーションファイルを作成
$	 rails	 generate	 migration	 CreateUsers
$	 ls	 db/migrate/
20131104023129_create_users.rb
バージョン番号として
タイムスタンプを使う
DBスキーマの変更操作を記述
##	 20131104023129_create_users.rb
class	 CreateUsers	 <	 ActiveRecord::Migration
	 	 def	 up
	 	 	 	 create_table	 "users"	 do	 |t|
	 	 	 	 	 	 t.string	 "name"
	 	 	 	 	 	 t.string	 "email"
バージョンを上げる
	 	 	 	 end
ときの操作
	 	 end
	 	 def	 down
	 	 	 	 drop_table	 "users"
バージョンを戻す
	 	 end
ときの操作
end
DBスキーマの変更操作を記述
##	 20131104023129_create_users.rb
class	 CreateUsers	 <	 ActiveRecord::Migration
	 	 def	 change
	 	 	 	 create_table	 "users"	 do	 |t|
	 	 	 	 	 	 t.string	 "name"
	 	 	 	 	 	 t.string	 "email"
簡単な操作なら、戻す
	 	 	 	 end
ときの操作を省略可能
(Railsが推測してくれる)
	 	 end
end
マイグレーションを実行
###	 バージョンを上げる
$	 rake	 db:migrate
###	 バージョンを戻す
$	 rake	 db:rollback
###	 再実行(戻して、もう一度上げる)
$	 rake	 db:migrate:redo
###	 現在のバージョンを調べる
$	 rake	 db:version
Current	 version:	 20131104023129
実行結果を確認
postgres=>	 dt	 users;
テーブルが作成された
	 	 	 	 	 	 	 List	 of	 relations
ことを確認
	 Schema	 |	 Name	 	 |	 Type	 	 |	 Owner
--------+-------+-------+-------	 public	 |	 users	 |	 table	 |	 myname
(1	 row)
postgres=>	 select	 *	 from	 schema_migrations;
	 	 	 	 version
---------------バージョン番号が専用の
	 20131104023129
テーブルに保存される
(1	 row)
管理サイクル
Step 1. マイグレーションファイルを作成
Step 2. 変更操作を手動で記述
・バージョンを上げるときの操作
・バージョンを戻すときの操作

Step 3. マイグレーションを実行
・DBスキーマが変更される
・現在のバージョン番号がDB内に保存される
これらの手順の積み重ねでバージョン管理を行う
(この手順から外れた方法で変更しないこと!)
ソースコード管理ツールとの違い
Git、Subversion

スキーマ管理ツール

・差分は「データ」(diff形式)

・差分は「操作」(DSL or SQL)

・ツールが自動計算

・人が手動で記述 ※

@@	 -1,4	 +1,5	 @@
	 #include	 <stdio.h>
-void	 main()	 {
+int	 main()	 {
	 	 	 printf("Hello");
+	 	 return	 0;
	 }

def	 change
	 	 create	 table	 "users"
	 	 	 	 t.string	 "name"
	 	 	 	 t.string	 "email"
	 	 	 	 ...
	 	 end
end
※ 支援機能を持つツールもあるが、あくまで「支援」だけ
はじめてのDBスキーマ管理

Migr8.rb 編(SQL派)
Migr8.rb

とは?

(マイグレイト.rb)

✓ お手軽なDBスキーマ管理ツール

※

・PostgreSQL、SQLite3、MySQLをサポート
・要Ruby
・https://github.com/kwatch/migr8

✓ 使うまでのしきいが低い
・ファイル1つだけ(他の余計な外部ライブラリが不要)
・設定ファイルがない(環境変数を2つ設定するだけ)
・SQLで記述(DSLの学習コストがかからない)

※ 実は、自作ツール :)
インストールとセットアップ
Install

$	 curl	 -Lo	 migr8.rb	 http://bit.ly/migr8_rb
$	 chmod	 a+x	 ./migr8.rb
設定ファイルがいらない

Setup
※

$	 export	 MIGR8_COMMAND="psql	 -q	 -U	 user	 db"
$	 export	 MIGR8_EDITOR="emacsclient"	 	 #	 or	 "vi"
$	 ./migr8.rb	 init
※ または export	 MIGR8_COMMAND="sqlite3	 dbfile"
(for SQLite3)
export	 MIGR8_COMMAND="mysql	 -s	 -u	 user	 db" (for MySQL)
マイグレーションファイルを作成
$	 migr8.rb	 new	 -m	 "create	 'users'	 table"
	 	 	 	 #	 or:	 migr8.rb	 new	 --table=users
$	 ls	 migr8/migrations
scjs8350.yaml
バージョン番号として
ランダム文字列を使う
(とても重要な特徴!)
DBスキーマの変更操作を記述
#	 -*-	 coding:	 utf-8	 -*version:	 	 	 	 	 scjs8350
desc:	 	 	 	 	 	 	 	 create	 'users'	 table
author:	 	 	 	 	 	 alice
vars:

バージョンを上げる
ときの操作

up:	 |
	 	 create	 table	 users	 (
	 	 	 	 id	 	 	 	 serial	 	 	 	 	 	 	 	 primary	 key,
	 	 	 	 name	 	 varchar(255)	 	 not	 null	 unique,
	 	 );
down:	 |
	 	 drop	 table	 users;

バージョンを戻す
ときの操作
DBスキーマの変更操作を記述
#	 -*-	 coding:	 utf-8	 -*version:	 	 	 	 	 scjs8350
desc:	 	 	 	 	 	 	 	 create	 'users'	 table
author:	 	 	 	 	 	 alice
変数を定義可能
vars:
	 	 -	 table:	 	 	 users
変数を展開

up:	 |
	 	 create	 table	 ${table}	 (
	 	 	 	 id	 	 	 	 serial	 	 	 	 	 	 	 	 primary	 key,
	 	 	 	 name	 	 varchar(255)	 	 not	 null	 unique,
	 	 );
変数を展開
down:	 |
	 	 drop	 table	 ${table};
マイグレーションを実行
###	 バージョンを上げる
$	 ./migr8.rb	 up
###	 バージョンを戻す
$	 ./migr8.rb	 down
###	 再実行(戻して、もう一度上げる)
$	 ./migr8.rb	 redo
###	 現在のバージョンを調べる
$	 ./migr8.rb	 status	 	 	 #	 省略可
...(snip)...
実行結果を確認
postgres=>	 dt	 users;
テーブルが作成された
	 	 	 	 	 	 	 List	 of	 relations
ことを確認
	 Schema	 |	 Name	 	 |	 Type	 	 |	 Owner
--------+-------+-------+-------	 public	 |	 users	 |	 table	 |	 myname
(1	 row)
postgres=>	 select	 *	 from	 _migr8_history;
	 version
---------バージョン番号が専用の
	 scjs8350
テーブルに保存される
(1	 row)
履歴を表示
$	 ./migr8.rb	 hist
scjs8350	 	 2013-11-07	 23:01:13	 	 #	 [alice]	 create	 'users'
ewwg6691	 	 2013-11-07	 23:29:33	 	 #	 [alice]	 add	 index
gnqc9473	 	 (not	 applied)	 	 	 	 	 	 	 	 #	 [john]	 create	 'groups'
spvo5800	 	 (not	 applied)	 	 	 	 	 	 	 	 #	 [john]	 add	 'group_id'
作成者と説明文
マイグレーションを適用した日時
(未適用なら "(not applied)")

適用すべきマイグレーションの一覧
ツール選択のポイント #1

DSL vs. SQL 
分類

ORM依存
South
Rails Alembic
CakePHP
Doctrine

Evolutions
(Play Framework)

SQL

DSL
Migr8
Flyway
PHPMigrate
Yoyo-migrations

Linquibase

ORM独立
比較
✓ DSL+ORM依存 (Railsなど)
・ORMや言語を乗り換えるのは難しい
・DBMSを乗り換えたり複数種類をサポートするのは簡単
・SQLでは困難でも、RubyやPHPでなら簡単に解決できることも

✓ SQL+ORM独立 (Migr8など)
・ORMや言語の乗換えがしやすい
・DBMSの乗り換えはほぼ無理(DDLがDBMS依存なため)

・困ったときにRubyやPHPでゴニョゴニョできない
どう選ぶ?
✓ ORMやFWに付属している場合
・それを使うしかない(DB管理者に選択権はない)
・DSLタイプでも生SQLを書ける機能はある(これが現実解?)

✓ ORMやFWに付属していない場合
・ORM非依存のを推奨
・PostgreSQLもMySQLもサポートしたい!→ DSLタイプ
・DBMSの乗り換えなんてしないよね∼ → 生SQLタイプでOK
ツール選択のポイント #2

バージョン番号の採番方法
バージョン番号に求められる性質
一意性

複数人で同時に開発作業をしても、バージョン番号が

順序性

番号の順番(=マイグレーションの適用順序)は一意

重ならないでほしい(重複しては困る)

に決まってほしい(人や環境によって違うのは困る)

不変性

一度決めたバージョン番号は変わらないでほしい
(primary keyなんだから値の変更は困る)
バージョン番号の採番方法
✓ 連番

却下
!

複数人での開発時に、番号が重複しやすい(一意性が低い)

✓ タイムスタンプ

却下
!

番号がそのまま順番を表すため、マイグレーションの適用順を変
えたい場合、番号の変更が必要(不変性が保たれない)

✓ ランダム文字列

採用
!

番号は一意性のみを担保し、順序性は別の方法で保持
→適用順を変更しても、番号の変更は必要ない(不変性を保持)
タイムスタンプだと困るケース
あるブランチで、
マイグレーション
を作成

別のブランチで、より
新しいタイムスタンプ
でマイグレーションを
作成し、

先にコミットした!

すると、古いタイムスタン
プのほうが、より新しいコ
ミットになってしまう

適用順 != タイプスタンプ順

Gitのコミット
ソースコード管理システムの進化
GitやMercurialの設計思想は
スキーマ管理にも応用できる

Git, Mercurial

※
(ランダム文字列)

進化

Subversion
(リポジトリ別の連番)
進化

CVS
(ファイル別の連番)

※ 正確には「コミットのハッシュ値」
ランダム文字列で順序性:Alembic
revision	 =	 'fa2cfc94fd'
down_revision	 =	 '547dcd1d3c30'

Alembic

マイグレーションファイルに、
戻り先のバージョン番号を記述

def	 upgrade():
(Gitのしくみとそっくり)
	 	 op.create_table('users',
	 	 	 	 Column('id',	 Integer,	 primary_key=True),
	 	 	 	 Column('name',	 String,	 nullable=False),
	 	 )
def	 downgrade():
	 	 op.drop_table('users')
ランダム文字列で順序性:Migr8
Migr8
#	 -*-	 coding:	 utf-8	 -*scjs8350	 	 	 #	 [alice]	 create	 'users'	 table
ewwg6691	 	 	 #	 [alice]	 add	 index	 to	 'name'	 col
gnqc9473	 	 	 #	 [john]	 create	 'groups'	 table
spvo5800	 	 	 #	 [john]	 add	 'group_id'	 to	 'users'
専用のテキストファイルに、
バージョン番号を順番に並べる
(マイグレーションファイルには書かない)
まとめ
まとめ
✓ DBスキーマのバージョン管理サイクル
・Step1. マイグレーションファイルを作成
・Step2. スキーマ変更操作を記述(up, down)
・Step3. マイグレーションを実行

✓ ツール選択のポイント
・DSL vs. 生SQL
・連番 vs. タイムスタンプ vs. ランダム文字列
おしまい

More Related Content

DBスキーマもバージョン管理したい!