これはデータベースのバージョン管理と database-as-code (GitOps) を扱うシリーズ記事です。
- データベースバージョン管理とは?
- データベースバージョン管理、State ベースか Migration ベースか? (本記事)
- Database as Code — 良いところ、悪いところ、見たくないところ
- Database as Code のランドスケープ
- データベースバージョン管理のベストプラクティス
Infrastructure as Code (IaC) の領域では、State ベースのアプローチが事実上の標準になりました。一方で、Database as Code (DaC) の領域では、チームは依然として Migration ベースのアプローチでスキーマを管理する傾向があります。本記事ではこの 2 つのアプローチの概要を示し、業界が分かれた背景にある論理を掘り下げます。最後に、Bytebase が両者の良いところをどう取り込めるかを紹介します。
Infrastructure as Code (IaC) と Database as Code (DaC) はどちらも Configuration as Code (CaC) に属し、構成ファイルを管理するアプローチには 2 つあります。
- State ベースのバージョン管理 (宣言的)
- Migration ベースのバージョン管理 (命令的)
State ベースのバージョン管理 (宣言的)
State ベースのアプローチは、スキーマ全体の最終的な望ましい状態をコードリポジトリに保存します。MySQL であれば、mysqldump で作成したスキーマダンプを保存することを意味します。
IaC では、Kubernetes、HashiCorp、Terraform といった主要なシステムがすべてこのアプローチを採用していることも見逃せません。
Migration ベースのバージョン管理 (命令的)
Migration ベースのアプローチは、マイグレーションスクリプトをリポジトリに保存します。各スクリプトには CREATE/ALTER/DROP TABLE のような DDL 文がまとまっています。望ましいスキーマ状態は、それらのスクリプトを決定的な順序で実行することで実現されます。
Migration ベースは、私たちが日常生活で物事を変えていくのと同じ手順を踏むため、より直感的です。
IaC でも、Kubernetes や Terraform 以前の時代は、命令的なコマンドを並べたシェルスクリプトでインフラをプロビジョニングするのが、どのチームでも普通の運用でした。
Infrastructure as Code は、業界がすでに State ベースへ移行した
State ベースの IaC にはいくつかの重要な利点があります。
- リポジトリ内に分かりやすい単一の真実の源 (SSOT) を保てる。構成は 1 つのソースファイルで表現される。Migration ベースでは、SSOT を多数のマイグレーションファイルから導き出す必要がある。
- 使い勝手。順序依存を気にせず、最終状態だけを記述できる方がシンプル。Kubernetes や Terraform のバックエンドが、システムを望ましい状態へ収束させる重い処理を引き受けてくれる。
Kubernetes や Terraform といったツールも、普及の触媒として働きました。
Database as Code では、Migration ベースが依然として主流
IaC とは対照的に、データベーススキーマの管理にはチームは依然として Migration ベースを好みます。一面では、これは供給側の事情によります。
- コードベースのスキーマ管理を支える既存ツールの大半が Migration ベースを採用している。
- 主要なアプリケーションフレームワークも同様で、いずれも Up/Down メソッドでスキーマ変更/ロールバックを管理している。これは生の SQL ではなく特定のプログラミング言語でマイグレーションを書く、別の表現でしかない。
しかし、データベース管理の領域では、なぜここ 5 年の間 IaC のように State ベースのシステムが普及しなかったのでしょうか?
理由はいくつかあると考えています。
システム側のサポート不足
State ベースか Migration ベースかはユーザー視点の話ですが、システム側の視点で見ると、State ベースを正しく実現するには遥かに多くの工学的努力が必要です。
Kubernetes は最初から State ベースを支えるコントローラーパターンを内蔵しています。データベースの世界はそうではありません。MySQL も PostgreSQL も、Kubernetes のように State ベースを支える組み込み機能を持っていません。システム自体がそうした仕組みを提供しないと、実現はかなり難しくなります。
データ (ステートフル) リソースの管理は、計算/ネットワーク (ステートレス) リソースの管理よりもはるかに複雑
HashiCorp Terraform も同様の問題を抱えているのでは、と疑問に思うかもしれません。Terraform が管理対象とするパブリッククラウドプロバイダーはブラックボックスだからです。
そう、だからこそ Terraform は名声に値するわけです:) Terraform はユーザーが望むインフラ状態とクラウドプロバイダーの実状態を内部で照合する複雑さを、見事にこなしています。
もう一つの理由として、State ベースは、破棄して作り直せる短命な計算/ネットワークリソースに本質的に向いている、という点があります。データベースはデータ (状態) を保持します。DaC で完全な State ベースを実現するには、IaC と同じスキーマ (メタデータ) の収束問題に加えて、データそのものの扱いも解決する必要があります。
Migration ベースの方が、State ベースよりチームが制御しやすい。データベースの誤りはコストが高すぎる
Migration ベースは、データベースに対して一手ずつコマンドを指示して変更を進めます。対して State ベースはブラックボックスのように見えることがあり、ときに想定外の結果を招きます。例を挙げます。
Alice と Bob の 2 人のエンジニアがいるとします。Alice がまずあるテーブルに columnA を追加するスキーマ変更を行います。一方の Bob は Alice の変更前のスキーマバージョンを起点に columnB を追加し、Alice の変更後にチェックインします。
State ベースでは、Bob のチェックインに含まれる望ましい状態は Alice の変更を含んでいないため、システムが Alice の作業を上書きしてしまいます。Migration ベースでは、変更が増分で適用されるため、この問題は起きません。
State ベースのバージョン管理は、衝突する変更を防ぐためにより多くのエンジニアリング規律とツールサポートを要求します。一方、Migration ベースは衝突に対してより寛容です。
データを扱うときの賭け金は大きく、人々は使い勝手を多少犠牲にしてでも、より制御しやすく安全側に倒れる方法を選びがちです。
Bytebase はどう支えるか
State ベースは Kubernetes、HashiCorp Terraform などの後押しで Infrastructure as Code を席巻しました。
しかしデータベースのバージョン管理については、現時点で正しいアプローチは Migration ベースだと考えています。これは MySQL、PostgreSQL のエンジン側サポートが不足していること、データ (アプリケーションの状態) を扱う複雑さ、そして失敗時のコストの高さに起因します。
同時に、State ベースの利点も理解しています。だからこそ Bytebase はバージョン 0.5.0でスキーマスナップショットと書き戻し機能を導入しました。
- すべてのスキーママイグレーションについて、Bytebase はスキーマスナップショットを記録する。
- チームがバージョン管理システムでデータベーススキーマを管理している場合、Bytebase に対し、指定したパスへスキーマスナップショットを書き戻すよう設定できる。
これにより、Bytebase はハイブリッドのスキーママイグレーションシステムになります。Bytebase を使うチームは Migration ベースの利点をすべて保ちながら、最新のスキーマファイルを 1 つ持てるようになります。このファイルがデータベーススキーマの単一の真実の源となり、State ベースの本質的な利点を取り込めます。設定方法はユーザーガイドを参照してください。
余談ですが、データベースエンジン自身が Kubernetes のような方向に進化し、State ベースへの道を整えてくれることを願っています。実は、Google 自身のデータベースシステムである Spanner はそうしたサポートを備えていて、Google の各チームはみな State ベースでデータベーススキーマを管理しています。
業界全体としてはまだそこに到達していませんが、少なくとも Bytebase はそのギャップを埋められます。