SchemaHeroでDBのschema migrationしてみた
はじめに
DBのschema管理といえばプロフェッショナルがDDL書いて流すレベルの高い作業の一つだという偏見を個人的に持っています。
私はDB分野の人ではないので、自分でやるとしたら緊張感に押しつぶされて誤ったDDLを書いてしまいそうです。
そこで今回はDDLよりいくらか簡単なYAMLでschemaを定義してshema管理を実現させるCloud Native Computing Foundation Sandbox ProjectsのOSSであるSchemaHeroをご紹介したいと思います。
本記事の準拠バージョン
本記事ではSchemaHero v0.17.5を利用します。
SchemaHeroをざっくり紹介
SchemaHeroは宣言的にDBのschema管理を実現するOSSで、平たく言えばYAMLで定義した状態のschemaを実現してくれるOSSです。(Ansibleのschema版とイメージすると分かりやすいかもしれません)
Cloud Native Computing Foundation Sandbox ProjectsのOSSでライセンスはApache-2.0 licenseです。
Kubernetes上で動作します。
ホームページ: https://schemahero.io/
GitHubリポジトリ:https://github.com/schemahero/schemahero
公式ドキュメント:https://schemahero.io/docs/
SchemaHeroが使えるDBサーバ・ライブラリ
SchemaHero v0.17.5では以下のDBがサポートされています。
- Postgresql 9.5 - 15.1
- Mysql 5.6 - 8.0
- Cockroachdb v19.2 - v22.1
- Cassandra 3.x
- SQLite 3.x
最新の対応状況、今後の対応ロードマップはこちらを参照してください。
https://schemahero.io/databases/
ORMとの比較
ORMとの比較はSchemaHero公式でも言及されています。
https://schemahero.io/learn/comparisons/orm/
要点としては
- SchemaHeroはschemaの管理、変更の追跡をより良い方法で行うためのもの
- SchemaHeroはshcema migration toolでORMのようなquery builderの機能はない
- 既にORMでschemaを管理できているのであればSchemaHeroを導入する理由はない
になります。
検証内容
チュートリアルの内容をたどり、SchemaHeroでschemaのデプロイ、変更を行う。
SchemaHeroの導入
SchemaHeroはクライアントソフトウェアからインストールする構成を取っています。
クライアントの導入
クライアントはkubectlのプラグインとして提供されていて、krew経由でインストールすることができます。
krewを導入していない場合は以下から先に導入しておいてください。
krewさえあれば導入はすぐにできます。
$ kubectl krew update
$ kubectl krew install schemahero
正常に導入されたかの確認はコマンドから
$ kubectl schemahero version
SchemaHero v0.17.5
といった形で確認できます。
SchemaHeroの導入
SchemaHero、SchemaHeroの利用するCRDの導入はクライアントから1コマンドで実行可能です。
$ kubectl schemahero install
以上です。
クライアントに依存せず、手動やGitリポジトリからYAMLを適用したい場合は
$ kubectl schemahero install --yaml
で同等のYAML定義を出力することができます。
導入結果はコマンドから
$ kubectl get pod -n schemahero-system
NAME READY STATUS RESTARTS AGE
schemahero-0 1/1 Running 0 12m
$ kubectl api-resources | grep schemahero.io
databases databases.schemahero.io/v1alpha4 true Database
migrations schemas.schemahero.io/v1alpha4 true Migration
tables schemas.schemahero.io/v1alpha4 true Table
views schemas.schemahero.io/v1alpha4 true View
といった形で確認できます。
チュートリアル実施用のDBとしてPostgreSQL podをデプロイ
新たにネームスペースを作成し、そこにチュートリアルで利用するDBとしてPostgreSQLをデプロイします。
$ kubectl create ns schemahero-tutorial
$ kubectl apply -n schemahero-tutorial -f https://raw.githubusercontent.com/schemahero/schemahero/main/examples/tutorial/postgresql/postgresql-11.8.0.yaml
実際にどのような定義がデプロイされるかは
を参照してください。
セットアップが正常かの判断はpodがrunningになっている事を確認した後、
$ kubectl exec -it -n schemahero-tutorial postgresql-0 -- psql -U airlinedb-user -d airlinedb
コマンドでログイン(パスワードはpassword)して接続できるか確認してください。
ログイン後は\dtコマンドで初期状態ではschemaが存在しないことが確認できます。
airlinedb=> \dt
Did not find any relations.
SchemaHeroをDBに接続する
実のところ、まだSchemaHeroとDBは接続されていません。
接続先と認証情報を与えてSchemaHeroをDBに接続させます。
- airline-db.yamlという名前のファイルを以下の内容で作成する
apiVersion: databases.schemahero.io/v1alpha4
kind: Database
metadata:
name: airlinedb
namespace: schemahero-tutorial
spec:
connection:
postgres: # 接続先の種類にPostgreSQLサーバを指定
uri: # uri形式で接続先、認証情報を指定
valueFrom:
secretKeyRef:
name: postgresql
key: uri
- このairline-db.yamlファイルをkubernetesに適用する
$ kubectl apply -f ./airline-db.yaml
airline-db.yamlの中身の解説ですが、airlinedbという名前のDatabaseカスタムリソースでSchemaHeroにDBの接続情報を渡しています。
中身はspec.connection.postgresでPostgreSQLサーバである事を示し、その中のuriで接続文字列を指定しています。
接続文字列はsecretリソースを参照していて、参照結果はpostgresql://airlinedb-user:password@postgresql:5432/airlinedbといった文字列になっています。
これでDBに接続するための情報がSchemaHeroに渡り、SchemaHeroがDBに接続できます。
SchemaHeroでtableを作成する
SchemaHero経由でこちらのDDLで作成されるtableと等しいtableを作成します
CREATE TABLE airport
(
code char(4) not null primary key,
name varchar(255)
)
SchemaHeroでtableはTableカスタムリソースで定義します
具体的な手順としては
- airport-table.yamlファイルを以下の内容で作成する
apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
name: airport
namespace: schemahero-tutorial
spec:
database: airlinedb # airline-db.yamlで定義した名前に揃える
name: airport # table名
schema:
postgres:
primaryKey: [code] # primalyKeyのcolumnを指定
columns: # columnの名前と型を定義
- name: code
type: char(4)
- name: name
type: varchar(255)
constraints:
notNull: true # not null制約を有効にする
- 上記のtable定義を適用する
$ kubectl apply -f ./airport-table.yaml
これでtable定義が作成されました。
次に、このtable定義をどう実現するかをレビューします。
- 承認待ちの変更を確認します。例として、ここではIDがeaa36efの変更があることがわかります。
$ kubectl schemahero get migrations -n schemahero-tutorial
ID DATABASE TABLE PLANNED EXECUTED APPROVED REJECTED
eaa36ef airlinedb airport 13m
- 具体的にどのような内容なのか見ていきます
$ kubectl schemahero describe migration eaa36ef -n schemahero-tutorial
Migration Name: eaa36ef
Generated DDL Statement (generated at 2024-03-02T11:41:54+09:00):
create table "airport" ("code" character (4), "name" character varying (255) not null, primary key ("code"))
To apply this migration:
kubectl schemahero -n schemahero-tutorial approve migration eaa36ef
To recalculate this migration against the current schema:
kubectl schemahero -n schemahero-tutorial recalculate migration eaa36ef
To deny and cancel this migration:
kubectl schemahero -n schemahero-tutorial reject migration eaa36ef
DDLとしてはcreate table "airport" ("code" character (4), "name" character varying (255) not null, primary key ("code"))が適用されるようです。
- 変更を承認します。
$ kubectl schemahero -n schemahero-tutorial approve migration eaa36ef
Migration eaa36ef approved
- ステータスが変わったか確認します。
$ kubectl schemahero get migrations -n schemahero-tutorial
ID DATABASE TABLE PLANNED EXECUTED APPROVED REJECTED
eaa36ef airlinedb airport 19m 52s 52s
- DB側のコマンドラインでtableが作成されているか確認します。
airlinedb=> \d+
List of relations
Schema | Name | Type | Owner | Size | Description
--------+---------+-------+----------------+---------+-------------
public | airport | table | airlinedb-user | 0 bytes |
(1 row)
airlinedb=> \d+ airport
Table "public.airport"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+------------------------+-----------+----------+---------+----------+--------------+-------------
code | character(4) | | not null | | extended | |
name | character varying(255) | | not null | | extended | |
Indexes:
"airport_pkey" PRIMARY KEY, btree (code)
上記と同様の手順でschedule-table.yamlを作成し、schedule tableを作成します。
- schedule-table.yamlファイルを以下の内容で作成する
apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
name: schedule
namespace: schemahero-tutorial
spec:
database: airlinedb
name: schedule
schema:
postgres:
primaryKey: [flight_num]
columns:
- name: flight_num
type: int
- name: origin
type: char(4)
constraints:
notNull: true
- name: destination
type: char(4)
constraints:
notNull: true
- name: departure_time
type: time
constraints:
notNull: true
- name: arrival_time
type: time
constraints:
notNull: true
- schedule-table.yamlファイルを適用する
$ kubectl apply -f ./schedule-table.yaml
- 変更をレビューする
$ kubectl schemahero get migrations -n schemahero-tutorial
ID DATABASE TABLE PLANNED EXECUTED APPROVED REJECTED
a9626a8 airlinedb schedule 10s
eaa36ef airlinedb airport 30m 11m 11m
$ kubectl schemahero describe migration a9626a8 -n schemahero-tutorial
Migration Name: a9626a8
Generated DDL Statement (generated at 2024-03-02T12:12:33+09:00):
create table "schedule" ("flight_num" integer, "origin" character (4) not null, "destination" character (4) not null, "departure_time" time not null, "arrival_time" time not null, primary key ("flight_num"))
To apply this migration:
kubectl schemahero -n schemahero-tutorial approve migration a9626a8
To recalculate this migration against the current schema:
kubectl schemahero -n schemahero-tutorial recalculate migration a9626a8
To deny and cancel this migration:
kubectl schemahero -n schemahero-tutorial reject migration a9626a8
- 変更を承認する
$ kubectl schemahero -n schemahero-tutorial approve migration a9626a8
Migration a9626a8 approved
- DB側でtableが作成されているか確認する
airlinedb=> \d+ schedule
Table "public.schedule"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
----------------+------------------------+-----------+----------+---------+----------+--------------+-------------
flight_num | integer | | not null | | plain | |
origin | character(4) | | not null | | extended | |
destination | character(4) | | not null | | extended | |
departure_time | time without time zone | | not null | | plain | |
arrival_time | time without time zone | | not null | | plain | |
Indexes:
"schedule_pkey" PRIMARY KEY, btree (flight_num)
これでSchemaHeroを通してtableを作成する事ができました!
Table定義の変更
先程作成したschedule tableのcolumnを変更します。
変更内容は
- departure_timeをnullableに
- arrival_timeをnullableに
- intが入るduration columnを追加する
になります。手順は
- 先ほど作成したschedule-table.yamlファイルを以下の内容に変更する
apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
name: schedule
namespace: schemahero-tutorial
spec:
database: airlinedb
name: schedule
schema:
postgres:
primaryKey: [flight_num]
columns:
- name: flight_num
type: int
- name: origin
type: char(4)
constraints:
notNull: true
- name: destination
type: char(4)
constraints:
notNull: true
- name: departure_time # constraintsを取り除く
type: time
- name: arrival_time # constraintsを取り除く
type: time
- name: duration # 追加分
type: int
- schedule-table.yamlファイルを適用する
$ kubectl apply -f ./schedule-table.yaml
- 変更をレビューする。
$ kubectl schemahero get migrations -n schemahero-tutorial
ID DATABASE TABLE PLANNED EXECUTED APPROVED REJECTED
a9626a8 airlinedb schedule 12m 9m37s 9m37s
eaa36ef airlinedb airport 43m 24m 24m
fa32022 airlinedb schedule 4s
$ kubectl schemahero describe migration fa32022 -n schemahero-tutorial
Migration Name: fa32022
Generated DDL Statement (generated at 2024-03-02T12:24:56+09:00):
alter table "schedule" alter column "departure_time" type time, alter column "departure_time" drop not null;
alter table "schedule" alter column "arrival_time" type time, alter column "arrival_time" drop not null;
alter table "schedule" add column "duration" integer
To apply this migration:
kubectl schemahero -n schemahero-tutorial approve migration fa32022
To recalculate this migration against the current schema:
kubectl schemahero -n schemahero-tutorial recalculate migration fa32022
To deny and cancel this migration:
kubectl schemahero -n schemahero-tutorial reject migration fa32022
alter tableが実行され、table定義を変更する内容です。
- 変更を承認する
$ kubectl schemahero -n schemahero-tutorial approve migration fa32022
Migration fa32022 approved
- DB側で変更が適用されているか確認する
airlinedb=> \d+ schedule
Table "public.schedule"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
----------------+------------------------+-----------+----------+---------+----------+--------------+-------------
flight_num | integer | | not null | | plain | |
origin | character(4) | | not null | | extended | |
destination | character(4) | | not null | | extended | |
departure_time | time without time zone | | | | plain | |
arrival_time | time without time zone | | | | plain | |
duration | integer | | | | plain | |
Indexes:
"schedule_pkey" PRIMARY KEY, btree (flight_num)
これでtableの定義を変えることができました。table作成の時と手順が変わらないので覚えやすいですね。
外部キー制約の追加
先程のschedule tableに外部キー制約を追加します。
具体的には以下を実現します。
- airport tableのcode columnをschedule tableのorigin columnの外部キーに指定する
- airport tableのcode columnをschedule tableのdestination columnの外部キーに指定する
実現方法は先程と同じく、schedule-table.yamlの変更と適用になります。
- schedule-table.yamlを以下の内容に変更する
apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
name: schedule
namespace: schemahero-tutorial
spec:
database: airlinedb
name: schedule
schema:
postgres:
primaryKey: [flight_num]
foreignKeys: # ここで外部キーを設定する
- columns:
- origin # 外部キー制約を適用するcolumn
references: # 外部キーとして利用するtableのcolumnを指定する
table: airport
columns:
- code
- columns:
- destination
references:
table: airport
columns:
- code
columns:
- name: flight_num
type: int
- name: origin
type: char(4)
constraints:
notNull: true
- name: destination
type: char(4)
constraints:
notNull: true
- name: departure_time
type: time
- name: arrival_time
type: time
- name: duration
type: int
- schedule-table.yamlファイルを適用する
$ kubectl apply -f ./schedule-table.yaml
- 変更をレビューする
$ kubectl schemahero get migrations -n schemahero-tutorial
ID DATABASE TABLE PLANNED EXECUTED APPROVED REJECTED
a9626a8 airlinedb schedule 27m 25m 25m
b12d3fd airlinedb schedule 40s
eaa36ef airlinedb airport 58m 39m 39m
fa32022 airlinedb schedule 15m 13m 13m
$ kubectl schemahero -n schemahero-tutorial describe migration b12d3fd
Migration Name: b12d3fd
Generated DDL Statement (generated at 2024-03-12T12:39:48+09:00):
alter table schedule add constraint schedule_origin_fkey foreign key ("origin") references "airport" ("code");
alter table schedule add constraint schedule_destination_fkey foreign key ("destination") references "airport" ("code")
To apply this migration:
kubectl schemahero -n schemahero-tutorial approve migration b12d3fd
To recalculate this migration against the current schema:
kubectl schemahero -n schemahero-tutorial recalculate migration b12d3fd
To deny and cancel this migration:
kubectl schemahero -n schemahero-tutorial reject migration b12d3fd
alter tableが実行され、外部キー制約が適用される内容になっています。
- 変更を承認する
$ kubectl schemahero -n schemahero-tutorial approve migration b12d3fd
Migration b12d3fd approved
- 変更が適用されているかDB側で確認する
airlinedb=> \d+ schedule
Table "public.schedule"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
----------------+------------------------+-----------+----------+---------+----------+--------------+-------------
flight_num | integer | | not null | | plain | |
origin | character(4) | | not null | | extended | |
destination | character(4) | | not null | | extended | |
departure_time | time without time zone | | | | plain | |
arrival_time | time without time zone | | | | plain | |
duration | integer | | | | plain | |
Indexes:
"schedule_pkey" PRIMARY KEY, btree (flight_num)
Foreign-key constraints:
"schedule_destination_fkey" FOREIGN KEY (destination) REFERENCES airport(code)
"schedule_origin_fkey" FOREIGN KEY (origin) REFERENCES airport(code)
これで外部キー制約が追加できました。
最後に
DDLが苦手な自分でもSchemaHeroでならschema管理できそうな気がしてきました。
各ORMの書き方や方言を覚えなくても慣れ親しんだYAMLで定義できるところは好印象でした。(構造は覚える/調べる必要がありますが...)
DBの知識は必要ですが、あまり構造を覚えなくとも他人の書いたYAMLをレビューすることも可能なのではないでしょうか。