きり丸の技術日記

技術検証したり、資格等をここに残していきます。

flywayで参照制約を追加してアプリ起動しなくなった話

やらかしたらブログにする。
一人でも犠牲者が減ればいいんや…。

ちなみに、今回は2つハマりました。

環境

  • Java
  • flyway
  • H2 (開発DB)
  • Postgres (本番DB)

事象

flywayで参照制約を追加したが、既存のデータが既に参照制約を違反した状態で存在しており、アプリサーバの起動が出来なくなった。

flywayとは

DBをマイグレーションしてくれるツール。

簡単に言うと、自動でDBのテーブル構成を構築できる。
ファイルのバージョンごとに実行してくれるので、テーブル作成後にインデックスを作成、といった処理順序が大事な処理も問題なく実行できる。

また、javaの起動時にflywayを起動できる。
これがあることで、review環境、検証環境、本番環境等の複数の環境があっても、DBの状態は常に目的通りの最新状態にできる。

更にメリットとしては、テストを実行するときも本番環境と同じ状態を簡単に作成できるので、環境構築の手間がほぼ0になる。

詳細事象

flywayを使うと自動でテーブル構築はできるのだが、過去ファイルの修正は厳禁となる。

typoを修正したり、カラムの型を変更したりといった問題も全部新規ファイルにし、ALTER TABLEで対応する必要がある。

これがとてもつらい。

今回、テーブルを別のテーブルにマッピングするようにして、参照制約を追加した。


Before

  • 受注
    • 明細
      • 商品

After

  • 受注
    • 明細
    • 商品

だが、ローカル実行したテストでは普通に動いていたので、データパッチをあてずに、そのままflywayで実行してしまった。

結果、参照性制約違反が発生し、起動しないアプリケーションが生まれてしまった。

SQL変更すればいいじゃん」って思うかもしれないが、もう遅い。
なぜなら、flywayではファイルの修正が厳禁だから。
具体的な処理としては、最初に読み込んだ状態のチェックサムが異なると、その時点でエラーになる。

一度、チェックサムの異常が出てしまうと、postgresを1から再構築しなければならなくなるので、本番環境なんかに流してしまった日には死あるのみ。


今回に関しては最悪、データが無い状態であれば正常に動くので、データパッチをあてれば起動することはできる。
が、それは各環境に手動パッチを当てなきゃならないことを意味するので、各環境のリリースするタイミングを調整しなければならない。

それは工数的にも心理的にもよろしくない。

今回に関しては、開発者だけが使用するサーバだけ壊してしまった。
とりあえず開発者グループに謝って、postgresを1から作成しなおし、flywayを修正して本番環境に適応した。


ちなみに、修正としてはこうなった。

Before

  • DROP 参照制約
  • CREATE 参照制約

After

  • DROP 参照制約
  • UPDATE データパッチ
  • CREATE 参照制約

ちなみに、マイグレーションが成功しなければ、ファイル修正しても復活させることができるらしい。
が、javaのライブラリとして使用していたので、その辺はどう処理できるかが分からない。

この辺、Java + flywayでのマイグレーション失敗時のリカバリ方法に詳しい人教えてほしいです…。

まぁ、今回に関しては、データパッチを当てて一度正常に処理終了させた後に、その方法は運用者にNGと言われたので手遅れだったんですが。

対策

  • ローカルで本番環境相当のデータを入れたうえで、動作確認を行ってから、参照制約を追加する
    • 正統派修正
  • 参照制約は手動で追加する。And 参照制約追加のバージョンは別にする。
    • ガバ修正

正直、本番相当のデータを使うのってデータの量もあるし、セキュリティ的にもできない可能性がある。
ガバ修正を人に推奨していると怒られるかもしれないが、マイグレーション失敗時のダメージがえぐいので回避策としてはありだと思ってる。

  1. V1 基本環境作成
  2. V2 DROP 参照制約
  3. 手動で参照制約追加
  4. V3 DROP AND CREATE 参照制約

だめですかね?


ちなみに、ここまでが前半戦。
ここからが後半戦。

データパッチをあてるのに苦労した話

上でも記載しましたが、参照制約のあるキーを変更しました。

商品テーブルを明細IDと紐づけていたのを、商品テーブルを受注IDと紐づけるようにしました。


Before

  • 受注
    • 明細
      • 商品

After

  • 受注
    • 明細
    • 商品

だから、データパッチの当て方として、別テーブルから取得した値を元に更新する必要があります。
商品テーブルの明細IDを元に明細テーブルを検索し、明細テーブルの受注IDを商品テーブルに更新する必要がありました。

別テーブルの値を取得して更新するUPDATE文って結構DBの方言があるんですね。
ローカルDBで確認しているのに、flywayで全く動かなくて非常に焦りました。

SELECT, UPDATE, INSERTには方言が無いと勝手に思い込んでいたので、3時間くらい色んな人に相談してました。
結果的に以下のようなSQLで実行できました。

-- H2で成功したSQL
UPDATE 商品 SET 参照キー = (
    SELECT 受注ID 
    FROM 明細 
    WHERE 明細.明細ID = 商品.参照キー
)

-- Postgresでは成功するSQL
UPDATE 商品 SET 商品.参照キー = 副.受注ID
FROM (
    SELECT 商品ID, 受注ID 
    FROM 商品, 明細 
    WHERE 
    明細.明細ID = 商品.参照キー
) 副
WHERE 
商品.商品ID = 副.商品ID

oracleもH2の文章で実行できそうなので、どちらかというとPostgresのほうが異端なんですかね…。

学んだこと

検索するときは対象のSQLがどの環境で動作するかをちゃんと確認する。
結局、公式ドキュメントが正義。

おわりに

この障害で5時間くらいかかってました。

一度この状態にしてしまうと、以降のリリースは全部失敗してしまうので、もうずっとテンパりながら対応してました。
落ち着いて対処したら3時間くらいで対応できたとは思います…。

flywayって便利ですけど、下手にやらかすと復活もできなくなるので怖いですね。
他の人はこんなことにならないように…。