そこに仁義はあるのか(仮)

略してそこ仁!

スキーマの変更をデプロイする方法

【翻訳記事】デプロイ戦略の定義のブログでアプリケーションをデプロイする方法のパターンについて書きました。その記事のはてブにコメントをいただいた(ありがとうございます!)のですが、データベースの変更をした際のデプロイ方法についても気になるというコメントがありました。
そこで、翻訳記事の元になったブログとは違いますが、今読んでいる書籍「進化的アーキテクチャ」にデータベースの変更をデプロイするパターンについて記載されていたので、それを抜粋してブログにまとめます。
と言っても、データベースの変更はアプリケーションほどデプロイ種類があるわけではありません。データはあるか、統合点(後ほど説明します)はあるかという観点で3つのパターンにまとめられています。
そして、【翻訳記事】デプロイ戦略の定義で書いたそれぞれのデプロイ戦略をとる場合、どのケースに当てはめると良さそうなのかも考えました。諸々、私の考えが反映されているので、そうではないプレーンな情報を見たい方は書籍の「5章 進化的データ」をご参照ください。

進化的アーキテクチャ ―絶え間ない変化を支える

進化的アーキテクチャ ―絶え間ない変化を支える

そもそも、今回紹介するスキーマ変更のパターンは、書籍「データベース・リファクタリング」にて説明されているexpand/contractパターンと呼ばれる一般的に利用されているリファクタリングパターンとのことです。

まず最初に:データベースの統合点とは

下の図のでは3つのアプリケーションで1つのデータベースを統合しています。アプリケーションA、B、Cとしていますが、アプリケーションのデプロイという観点ではそれぞれをアプリのバージョンと見るとイメージがつきやすいかもしれません。
多くのプロジェクトで、このように複数のアプリケーションでデータベースを統合して使うケースが多いかと思います。(ただし、マイクロサービスアーキテクチャでは、サービスごとにデータベースを分けることが推奨されています。念のため。)
このように、データベースをアプリケーションの統合点としているかどうかが、今回重要な観点になります。
アプリケーションの統合点をデータベースが持っているときには、アプリケーションの1つの変更のためにスキーマを変更しなければならない場合に、スキーマの変更が他のアプリケーションを壊してしまう可能性があります。

f:id:syobochim:20200518224734p:plain

expand / contractパターンとは

expand / contractパターンとは、下の図のようにデータベースのリファクタリングをするときに移行フェーズを設けることで、タイミング問題を回避します。
このパターンでは、移行開始時と移行終了時の2つのタイミングをもち、移行中は古い状態と新しい状態の両方を維持します。それによって、移行状態では後方互換性を維持でき、他のシステムが変更に追いつくための十分な時間を与えます。
組織によっては、移行状態が数日から数ヶ月かかる可能性もあります。しかし、サポートされるバージョンの数は厳しく制限すべきです。より多くのバージョンはより多くのテストやアーキテクチャ上の負担を生じさせます。一度にサポートするのは2つのバージョンまでとした方が良いでしょう。

f:id:syobochim:20200518224748p:plain

expand / contract パターンの選択肢

では、上の図のように、CustomerテーブルのName項目をFirstName / LastNameへ変更する場合を例として、expand / contractパターンで取れるいくつかの選択肢を使って説明していきます。
また、アプリケーションの「バージョン」という観点でデータベースが統合されるとしたときに、どのデプロイ戦略に当てはめられそうかも書いていきます。
それぞれのデプロイ戦略についての詳細は【翻訳記事】デプロイ戦略の定義を参照してください。

選択肢1 : 統合点も従来のデータもない

統合点はないため、開発者は他のシステムについて考慮する必要はなく、管理している既存のデータもありません。
この場合は、移行期間は必要なく、新しい列を追加して古い列を削除すればOKです。
以下のSQLのように、テーブルに対して新しいカラムを追加して削除すれば作業は完了です。

ALTER TABLE Customer ADD firstname VARCHAR2(60);
ALTER TABLE Customer ADD lastname VARCHAR2(60);
ALTER TABLE Customer DROP COLUMN name;
アプリケーションのデプロイ戦略では

このケースでは、古いものを壊して新しいものを構築する、「無謀なデプロイ (Reckless Deployment)」似て使うのが良いでしょう。

選択肢2 : レガシーデータはあるが、統合点はない

既存のデータを新しいカラムに移行する必要がありますが、他のアプリケーションを気にする必要はありません。他のアプリケーションは既存のカラムから適切な情報を抽出し、データの移行を行う関数を作成する必要があります。
既存のデータを抽出して移行が必要ですが、割と単純なケースになります。
以下のSQLのようにテーブルのカラムを変更し、データを移せば作業は完了です。

ALTER TABLE Customer ADD firstname VARCHAR2(60);
ALTER TABLE Customer ADD lastname VARCHAR2(60);
UPDATE Customer set firstname = extractfirstname(name);
UPDATE Customer set lastname = extractlastname(name);
ALTER TABLE Customer DROP COLUMN name;
アプリケーションのデプロイ戦略では

現在の環境に影響を与えず、分割された新しい環境に新しいバージョンをデプロイする「ブルーグリーンデプロイ (Blue/Green Deployment)」では、このケースが使えると思います。
ただし、アプリケーションを移行する際のデータの差分については考慮する必要があります。

選択肢3 : 既存のデータがあり、統合点もある

これが最も複雑で、最もよくあるシナリオです、古いスキーマに依存するアプリケーションが新しいカラムを使うようになるまではシステム移行が完了しません。
以下のSQLでは、テーブル変更やデータの移行後、データベースにトリガーを追加し、データを挿入する際に古いnameカラムから新しいfirstname / lastnameカラムにデータを映すようにしています。また、新しいシステムがデータを挿入する際にも、firstname / lastnameをつなげてnameカラムに保存し、他のアプリケーションが適切にフォーマットされたデータにアクセスできるようにしています。

ALTER TABLE Customer ADD firstname VARCHAR2(60);
ALTER TABLE Customer ADD lastname VARCHAR2(60);
UPDATE Customer set firstname = extractfirstname(name);
UPDATE Customer set lastname = extractlastname(name);

CREATE OR REPLACE TRIGGER SynchronizeName
BEFORE INSERT OR UPDATE ON Customer
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
BEGIN
  IF :NEW.Name IS NULL THEN
    :NEW.Name := :NEW.firstname||' '||:NEW.lastname:
  END IF;
  IF :NEW.NAME IS NOT NULL THEN
    :NEW.firstname := extractfirstname(name);
    :NEW.lastname := extractlastname(name);
  END IF;
END;

他のアプリケーションも新しいスキーマを使うよう移行が完了すると、以下のSQLでカラムを削除します。

ALTER TABLE Customer DROP COLUMN name;
アプリケーションのデプロイ戦略では

複数のバージョンから参照される可能性のある以下のデプロイ方式の時には、この戦略を採用することになるでしょう。

まとめ

前回ブログに書いた【翻訳記事】デプロイ戦略の定義と、書籍「進化的アーキテクチャ」の「5章 進化的データ」をまとめてみました。
データベースは既存のデータやアプリケーションの統合(バージョン的統合も含めて)から逃れられないケースが多いと思うので、expand / contractパターンを使うことが多いのではないでしょうか。
データベースも含めたデプロイ戦略の整理の助けになれば幸いです。

進化的アーキテクチャ ―絶え間ない変化を支える

進化的アーキテクチャ ―絶え間ない変化を支える