AlterCheck

Alto DBFlute のパートです。

概要

ReplaceSchema で差分DDLを検証する仕組みがあります(@since 0.9.8.4)AlterCheck と呼ばれるこの仕組みを利用してフルDDLと差分DDLをしっかりと整合性をとって運用していくことが推奨されます。

前のDB(Previous-DB) + 差分DDL(AlterDDL) = 最新のフルDDL(NextDDL)

前のDBに差分DDLを加えたものと、最新のフルDDLの実行結果は同じはずです。ReplaceSchema で前のDBの状態に戻して差分DDLを実行し、その後で最新のフルDDLで最新のDBを作って差分をチェックします。この課程を自動化します。

言葉の定義

PreviousDDL
前の状態のスキーマをゼロから復元するためのフルDDL (+ データ)
playsql/migration/previous 配下
AlterDDL
本番に適用させる予定の前の状態のスキーマを最新にする差分DDL (+ スクリプト)
playsql/migration/alter 配下
NextDDL
最新の状態のスキーマをゼロから作成するフルDDL (+ データ)
playsql 配下 ※いつもの場所、本線(メイン)のDDL

チェックの流れ

大きく二つのステップがあります。

PreviousDDL を保存
Previous-DB を再現するためのDDLをコピー保存 (前回のリリース後からDB変更前に)
AlterCheck を実行
Previous-DB + AlterDDL と NextDDL の結果をチェック (DB変更後からリリース前)

AlterCheck概念図

図 : AlterCheck概念図 AlterCheck概念図

そもそもいま本番とズレてない?

一番最初の導入したて AlterCheck の場合に、そもそも現時点で本番DBと開発DBがズレてしまっていては、いくら AlterCheck をしてもズレてる状態をキープし続けるだけです。SchemaSyncCheck を使ってチェックしてズレがあれば直すと良いでしょう。

さて、本番DBと開発DBにズレがなくなれば、ここからが AlterCheck の活躍の場面です。

まずは SavePrevious

DB変更する前に、今を保存

DB変更する前(前回リリース直後)に、DB変更前のDBを復元できる DDL やデータをまるごと保存します。これが(リリースするまでは)本番DBと同じ構造を表現する PreviousDDL となります。

Manageタスクで、SavePrevious を指定して実行します(@since 0.9.9.7B)

e.g. Manageタスクで SavePrevious の実行 (シェル) @Command
...$ sh manage.sh save-previous

 or

...$ sh manage.sh   // Enter, and then select the number

ZIP形式で保存される

すると、playsql 直下の DDL やデータがまるごと playsql/migration/previous 配下にコピー保存されます。さらに、妥当性をチェックするために内部的に ReplaceSchema が実行されます。正常に終了した場合は、migration 配下に previous-OK.dfmark (単に OK を強調するマーク) が作成されます。

e.g. PreviousDDL が正常に保存された場合 @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-previous
    |  |  |-previous-20120804-1746.zip // saved at 2012/08/04 17:46
    |  |-previous-OK.dfmark

万が一、開発前にこの課程を忘れてしまった場合でも、後でバージョン管理システムから直前のバージョンを取得して SavePrevious しても AlterCheck は利用できます。本番環境のDDLダンプでも構いません。要は migration/previous 配下に Previous-DB を復元できる DDL が手動でも配置されればOKなのです。 また、ZIP形式でなくても、previous配下に直接 replace-schema.sql を配置すれば AlterCheck 時に認識されます。 (内部的には、ZIPファイルがなければ展開されているファイルをそのまま利用します)

さてこの後、思う存分DB変更をして開発を続けてください。

AlterCheck の実行

そろそろ alter 文を作らないとぅ

DB変更した後、開発が落ち着いてDB構造も固まって、"そろそろ alter 文を作らないとぅ" というタイミングにて、現在の最新状態(NextDDL)と Previous-DB + AlterDDL の整合性をチェックします。

まずは、ウォーミングアップ

alter文はどこに書けばいいのか!? 差分ってどんな風にみれるのか!? まずは何も考えず何の準備もせず、そのままAlterCheckタスクをいきなり実行してみてみましょう。

Manageタスクで、AlterCheck を指定して実行します(@since 0.9.9.7B)

e.g. Manageタスクで AlterCheck の実行 (シェル) @Command
...$ sh manage.sh alter-check

 or

...$ sh manage.sh   // Enter, and then select the number

AlterCheckタスクは中断され(Failure)、前のスキーマと今のスキーマの純粋なDB変更の差分が検知されます。その差分は migration/schema 配下の alter-check-result.html に出力されます。

e.g. AlterCheck の結果 @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-...
    |  |-schema
    |     |-alter-check-result.html

この時点で、playsql/migration配下に alter というディレクトリが自動で作成され、デフォルトのAlterDDLのファイルも作成されます。 (あらかじめ手動で作成しても構いません。内部的にはファイルがなければ空っぽで作成しておいて、そのまま空のAlterDDLとして差分チェックをしているだけです)

e.g. AlterDDL の配置 @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema.sql
    |  |-previous
    |  |-...

alter文を書いてチェック

このSQLファイルに、今回のDB変更で本番環境に適用する予定の AlterDDL を記述します。AlterDDL のファイル名は、alter-schema で始まり .sql で終わる名前にします。必要に応じて、複数のファイルに分割が可能です。

AlterDDL を作成した状態でAlterCheckタスクを実行すると、Previous-DB に AlterDDL を適用した結果と、NextDDL の結果が比較され、もし結果に不整合(食い違い)がある場合は AlterCheck は中断され、その差分が先ほどの alter-check-result.html に出力されます。差分がなくなるまで AlterDDL を修正してはチェックしてを繰り返します。

この差分結果は、HistoryHTML で利用されているものと同様のものです。ここでは、"Previous-DB + AlterDDL" から "NextDDL" への差、ということで出力されます。例えば、テーブルが追加されている(ADD)とある場合、"AlterDDL でそのテーブルを ADD するように修正するべし" と解釈できます。

Previous
Previous-DB + AlterDDL
Next
NextDDL (期待値)

また、差分ルールは HistoryHTML の仕様と同じです。例えば、"カラム定義順序の違い" や "同じ構造で制約名だけの変更" など、基本的にアプリに影響のない微修正ものに関しては差として認識されません。

ちなみに、このSQLファイルのエンコーディングは UTF-8 で、insert文などのデータ値の中に改行が含まれていたら、その改行は "LF" として扱われます。(ReplaceSchemaのデータ登録で利用されるエクセルデータの改行もLFとして扱われます)

また、CraftDiffを利用している場合は、その差分も AlterCheck でチェックされます。AlterCheck の時の CraftDiff のTSVファイルは、playsql/migration/schema/craftdiff 配下に出力されます。

差分がなくなったら

差分がなくなったらAlterCheckタスクは正常終了し、その AlterDDL は妥当性のチェック済みとして migration/history 配下に保存されます(alter配下から移動します)。 ZIPファイル名には、どの PreviousDB に対応する AlterDDL なのかがわかるように、PreviousDDLの日付が入っています。

e.g. AlterDDLが保存されるHistory @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-alter // SQL files was moved to history
    |  |-previous
    |  |-history
    |  |  |-201208
    |  |     |-20120806_1023 // saved at 2012/08/06 10:23
    |  |        |-checked-alter-to-20120804-1746.zip // for 2012/08/04 17:46
    |  |-schema

正常終了後のDBの状態

AlterCheck正常終了後のDBは、PreviousDB に AlterDDL を適用させたものとなっています。 そのままアプリを起動してテストを行えば、より本番に近い状態のテスト環境として利用できます。

エラー発生時のログ

タスク実行がエラーになったとき、原因が実行時エラーなのか、不整合なのか、または別のエラーなのか、コンソールやタスクログの 最後のログメッセージ(Final Message) でそれらを確認することができます。

0.9.9.7B, 0.9.9.7A より前では...

0.9.9.7B より前のバージョンでは、Manageタスクでの実行ではなく、migraton配下に save-previous.dfmark というファイル(中身は空でよい)を作成して、ReplaceSchemaタスクを実行すると SavePrevious が実行されました。 まあ、alter配下に AlterDDL を配置した状態でReplaceSchemaタスクを実行すると AlterCheck が実行されました。

AlterCheckは、ReplaceSchemaの仕組みを利用していますが、ReplaceSchemaとは目的も運用タイミングも違うので、 明示的に別のタスクとして独立しました。

また、0.9.9.7A より前のバージョンでは、ZIP形式ではなく一つ一つのファイルがそのまま保存されます。 新しいバージョンでも "ZIPがなければ一つ一つのファイルを探す" 仕様になっているので、スムーズなバージョン移行は可能です。

そもそもDDLがNGだったら

PreviousNGマーク

そもそも PreviousDDL が文法的に間違っていて実行時にエラーとなった場合、および、不整合があって差分が発生した場合、 previous-NG.dfmark が作成されます。再実行で SavePrevious が正常に終了したら自動的に削除されます。

e.g. PreviousNGマーク @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-previous-NG.dfmark

AlterNGマーク

そもそも AlterDDL が文法的に間違っていて実行時にエラーとなった場合、および、不整合があって差分が発生した場合、 migration 配下に alter-NG.dfmark というファイルが作成されます。これは単に AlterDDL がダメだったよ、ということをおおげさに表現するマークファイルです。AlterCheck が成功したら自動的に削除されます。

e.g. AlterNGマーク @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema-10-basic.sql
    |  |  |-alter-schema-20-view.sql
    |  |-alter-NG.dfmark

NextNGマーク

そもそも AlterCheck 実行時の最新状態、つまり playsql 直下のDDLやデータが文法的に間違っていて実行時にエラーとなる場合、 migration 配下に next-NG.dfmark というファイルが作成されます。これは単に NextDDL がダメだったよ、ということをおおげさに表現するマークファイルです。AlterCheck が成功したら自動的に削除されます。

e.g. NextNGマーク @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema-10-basic.sql
    |  |  |-alter-schema-20-view.sql
    |  |-next-NG.dfmark

NGマークは NG の強調

そもそもNGマークの役割についてですが、単なる NG であることの強調という意味に尽きます。

しっかりとコンソールや dbflute.log でエラーメッセージを見てアクションを取って欲しいのですが、 あまりコンソールを見ずに(エラーに気付かずに)先に進んでしまうこともありえるので、おおげさにマークファイルを出力しています。

チェック後にまたDB変更があったら

DB変更してAlterDDLも作ってAlterCheckもしてさあ本番環境にリリース待ちという状態で、 またDB変更が入ってしまうということはよくあります。その場合は、チェック済みのAlterDDLにもう一度修正を入れる必要があります。

新たなDB変更の後、いきなりAlterCheckを実行してみてください。 直近のチェック済みのAlterDDLと新たなDB変更が入ったNextDDLが比較されます。 新たな差分が出力されると同時に、history上の直近のチェック済みのAlterDDLがalterディレクトリ配下に自動で復活します。 そのAlterDDLを修正し、差分がなくなるまでAlterCheckを繰り返します。

e.g. AlterDDLが保存されるHistory @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema.sql // revived from checked alter
    |  |-previous
    |  |-history
    |  |  |-201208
    |  |     |-20120806_1023
    |  |     |  |-checked-alter-to-20120804-1746.zip
    |  |-schema

差分がなくなったら、また新たなチェック済みのAlterDDLとしてhistoryに保存されます。 また、一つ前のチェック済みAlterDDLは不要なので、スキップされたことを示すZIPファイル名に変更されます。

e.g. AlterDDLが保存されるHistory @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |-previous
    |  |-history
    |  |  |-201208
    |  |     |-20120806_1023
    |  |     |  |-skipped-alter-to-20120804-1746.zip
    |  |     |-20120807_2136
    |  |        |-checked-alter-to-20120804-1746.zip
    |  |-schema

正常に運用していれば checked のZIPファイルは必ず一つしかできません。 (DB変更リリース済みの checked のZIPファイルは、SavePrevious で finished に変更される)

DB変更を本番環境にリリースしたら

次のDB変更に備えて、SavePrevious をしておきましょう。 忘れてDB変更をしてしまうと、PreviousDDLをバージョン管理システムなどからわざわざ復帰させる必要がありますので、 DB変更の予定がなくても保存しておくと良いでしょう。

その際、既に本番にリリース済み(であるはず)の checked のZIPファイルは、checked 部分のが finished に変更され、リリース済みであることがわかるようになります。

e.g. 本番リリース後のSavePreviousでcheckedがfinishedに @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |-previous
    |  |  |-previous-20120807-0108.zip
    |  |-history
    |  |  |-201208
    |  |     |-20120806_1023
    |  |     |  |-skipped-alter-to-20120804-1746.zip
    |  |     |-20120807_2136
    |  |        |-finished-alter-to-20120804-1746.zip
    |  |-schema

この状態で AlterCheck タスクを実行すれば、 alterディレクトリ配下にデフォルトの空っぽのSQLファイル(alter-schema.sql)が作成され、 新たなDB変更への旅のはじまりとなります。逆に、alter配下にSQLファイルがなく、history に checked が残っているときに AlterCheck を実行すると、直近の checked のAlterDDLが復帰し、"チェック後にまたDB変更があったら" の流れになってしまいます。

ゆえに、正常に運用していれば checked のZIPファイルは必ず一つしかできません。

AlterDDL で外部スクリプト

AlterDDL において、SQLの実行だけでなく外部スクリプトを実行することができます。 DBへの差分反映は、単純なSQLで済まされない場合もあります。DBMSのコマンドを利用して特殊な操作をすることもあるでしょう。 そういう場合のために、外部スクリプトが実行できるようになっています。

AlterDDL のSQLファイルと同じディレクトリに、alter-schema で始まる拡張子が .bat もしくは .sh のスクリプトファイルを配置します。SQLファイルも含めてファイル名の昇順で実行されます。

e.g. AlterDDL の外部スクリプトの配置 @playsql
dbflute_exampledb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema-10-basic.sql
    |  |  |-alter-schema-20-view.sql
    |  |  |-alter-schema-30-special.sh

スクリプトの exitCode が 0 以外の場合は、スクリプト実行エラーとみなされます。

スクリプトのコンソールログは、固定で UTF-8 として取得され、DBFluteタスクのログと一緒に出力されます。なので場合によっては、ログが文字化けする可能性はありますが、処理に影響はありません。

あえて AlterDDL はベタに

AlterDDL の実装方法として、SQLファイルと外部スクリプトというベタな方式に絞っているのは、本番環境への適用を考えてのことです。 本番環境のDBは、多くの場合開発者の手の届かないところにあります。 また、DBFluteを本番環境のDBが見える場所に配置して直接接続できるというようなことも少ないでしょう。 それはセキュリティのことを考えると当然のこととも言えます。

というわけで、AlterCheck の時点で DBFlute に依存した形式でチェックをしてもあまり意味がないとも言えます。 例えば、DB変更の一貫として必要となるデータをエクセルデータやTSVデータなどで管理して DBFlute の機能で登録しても(そういった機能は実装可能ですが)、本番環境では別の方法(DBMS のコマンドなど)で追加する必要があるかもしれません。

そういうことから、あえて AlterDDL はベタに実装することを想定した仕様にしています。 ただ、この本番環境適用は、そもそも多種多様でまだまだ分析の余地のある領域でもあるため、 今後の分析次第ではまた違った解釈で色々なアプローチを検討していくかもしれません。(2011/05/01現在)

DDLのDIFFにも利用できる

AlterCheck は本来の目的とは別に、単に 二つのDDLの構造の差を確かめる する用途にも利用できます。例えば、同じスキーマを作成する手動で作成されたDDLとツールで自動生成されたDDL、 これらが同じ結果をもたらすかどうか確認できます。