🔧

Cloud Spannerのスキーママイグレーションツールを作ってみた

2025/02/04に公開

はじめに

Cloud Spannerのスキーママイグレーションツールをシンプルな仕組みで作れるんじゃないかと思って作ってみたら結構ちゃんと動いてくれました。そこで作ったツールの紹介とその作り方を説明します。なお、作り方に関してはSpanner以外のデータベースにも適用できるのではないかと思っているので、別のデータベース用のマイグレーションツールを作ってみたい方は参考にしてみてください。

ツールの概要

spannerdiffというコマンドを作りました。以下のリポジトリです。

https://github.com/morikuni/spannerdiff

SpannerのDDLが2つあるときにDDLの差分を検出して、その差分を埋めるためのDDLを生成するというツールになっています。具体例を見てみましょう。

old.sql
CREATE TABLE Test (
    ID STRING(64) NOT NULL,
    Name STRING(64) NOT NULL,
) PRIMARY KEY (ID);

CREATE INDEX Test_Name ON Test (Name);
new.sql
CREATE TABLE Test (
    ID STRING(64) NOT NULL,
    Name STRING(64) NOT NULL,
    CreatedAt TIMESTAMP NOT NULL DEFAULT (CURRENT_TIMESTAMP()),
) PRIMARY KEY (ID);

CREATE INDEX Test_Name_CreatedAt ON Test (Name, CreatedAt DESC);
$ spannerdiff --base-file=old.sql --target-file=new.sql
result
DROP INDEX Test_Name;

ALTER TABLE Test ADD COLUMN CreatedAt TIMESTAMP NOT NULL DEFAULT (CURRENT_TIMESTAMP());

CREATE INDEX Test_Name_CreatedAt ON Test(Name, CreatedAt DESC);

このように、元のスキーマ(old.sql)から新しいスキーマ(new.sql)に移行するために必要なDDLを生成してくれます。

ツールの特徴

対応するDDLが多い

たぶんスキーマファイルに書きそうなDDLはほとんど網羅していると思います。以下が対応しているDDLです。

CREATE SCHEMA
CREATE TABLE
CREATE INDEX
CREATE SEARCH INDEX
CREATE PROPERTY GRAPH
CREATE VIEW
CREATE CHANGE STREAM
CREATE SEQUENCE
CREATE VECTOR INDEX
CREATE MODEL
CREATE PROTO BUNDLE
CREATE ROLE
GRANT
ALTER DATABASE

色つきのDDL出力

以下のように出力されるスキーマに色が付くようになっているので視覚的な判別がしやすいと思います。

追加系は緑、更新系は黄、削除系は赤という色分けをしています。

ツールの実装方法

このツールはGoで書いたのですが、実装の肝となるのは以下のinterfaceです。

type definition interface {
	id() identifier
	astNode() ast.Node
	add() ast.DDL
	drop() optional[ast.DDL]
	alter(target definition, m *migration)
	dependsOn() []identifier
	onDependencyChange(me, dependency migrationState, m *migration)
}

このツールがやっていることをすごく簡単にすると

  1. 新旧2つのDDLをASTに変換する
  2. 新しい定義であれば addd() を呼ぶ
  3. 消えた定義であれば drop() を呼ぶ
  4. なにか変更があった定義であれば alter() を呼ぶ
  5. dependsOn() にある依存先が変化した場合には onDependencyChange() が呼ばれる
  6. dependsOn() の依存関係をつかってDDLを並び替える

ということをやっています。
"定義" = definitionとはなにかというと、DDL中で識別可能なものです。基本的にはCREATE TABLEのような1 DDLに対応していることが多いです。ただし、テーブルのカラムは1カラム毎にdefinitionとして扱っています。

このdefinitionを設計のコアとすることで新しいDDLへの対応がかなり簡単にできるようになっています。逆に、この設計でマイグレーションできないDDLの変更には対応できません。いまのところそういうケースは思いついていないのですが、でてきたらこのツールはお終いです。

definitionによるマイグレーション実装の具体例

以下のPull RequestはCREATE CHANGE STREAMに対応したときのものなのですが、中を見てもらうとかなり簡単に実装できていると思います。(なお正しくマイグレーションできている保証はありません)

https://github.com/morikuni/spannerdiff/pull/4

ほかにも「Support CREATE ***」のようなPull Requestを見てもらえば新しいDDLに対応するのがかなり簡単になっているのがわかると思います。

スキーママイグレーションツールを実装しようと思うと差分のDDLを吐き出すというそれ自体よりも、依存関係を解決することの難易度が高いんじゃないかと思います。spannerdiffではそこをうまいこと仕組み化してしまうことで、definitionだけ実装すれば新しいDDLに対応できるようになりました。
これによってツールの特徴に挙げた「対応するDDLが多い」ということが実現できています。

ほかのデータベースへの対応

データベースが変わっても基本的なマイグレーション方法は変わらないと思うので、もし別のデータベースのマイグレーションツールを作りたいという方はspannerdiffを参考すると簡単に作れるんじゃないかと思っています。
ただし、前提としてDDLをパースしてASTに変換するライブラリが必要です。こちらの方が自作は難しいし、存在しないことも多いと思います。spannerdiffでは以下のmemefishというライブラリを使用しました。

https://github.com/cloudspannerecosystem/memefish

ASTに変換できるライブラリさえあれば、あとはASTからdefinitionへの変換だけすれば大部分は流用して実装ができるのではないかと思います。

おわりに

作ってから1ヶ月くらいは自分でドッグフーディングしましたが、ほとんどTABLEとINDEXくらいしか使っていないので、あまり検証はできていません。挙動がおかしい部分があれば是非Issueを作って教えてほしいです!

実装についての説明はマイグレーション部分についてだけにしましたが、spannerdiffのリポジトリを見てもらうと

  • ターミナル上でのSQLのシンタックスハイライト
  • GoReleaserによる自動リリース
  • GoReleaserによるHomebrew対応

など、Goのツールを作るときに参考になりそうなものがいくつかあるので、興味のある方は見てみてください。

Discussion