Python Migration Toolを徹底比較してみた

# 概要
Djangoのようなフレームワークを使う際にはMigration Toolが内蔵されていますので特にどのツールを使おうか悩むことはないかと思いますが、FastAPIやFlaskなどの軽量フレームワークを使う際、Migration Toolを選定する必要があるかと思います。

そんな方のために現時点において主要な三つのPythonのMigration Toolをまとめてみました。

(2021/2/20現在)

|   |Github|Github Stars  |Github最終更新日  |対応DB|DSL/RawSQL|ドキュメント|
|---|---|---|---|---|---|---|
|alembic  |https://github.com/sqlalchemy/alembic  |834  |11 hours ago|MySQL, SQLServer,Postgresql, SQLite | Python/Raw SQL |https://alembic.sqlalchemy.org/en/latest/ | |
|simple-db-migrate  |https://github.com/guilhermechapiewski/simple-db-migrate  |186  |5 months ago|MySQL|Raw SQL|Githubのみ|
|yoyo-migration  |https://github.com/marcosschroh/yoyo-database-migrations  |9|13 months ago|PostgreSQL, MySQL, SQLite|Python / Raw SQL|https://ollycope.com/software/yoyo/latest/|

ざっと比較表を見るとalembicが圧倒的ですね。
実際Flaskによく使われるFlask-migrationもalembicをwrapしたものであり、Flaskに次ぐ軽量フレームワークのFastAPIも公式はalembicを推奨しています(https://fastapi.tiangolo.com/ja/tutorial/sql-databases/#alembic-note)

現時点においてはalembicとsimple-db-migrateが二大勢力といった感じでしょうか。
ですのでこの記事では主要な二つのPython Migration Toolであるalembicとsimple-db-migrateを比較していこうと思います！

それではまずalembicから使い方とメリデメを見ていきましょう！



# alembic
> Alembic is a database migrations tool written by the author of SQLAlchemy.

とGithubのREADMEに書いてあるとおり、sqlalchemyの作者が作っただけあってsqlalchemyとの互換性は抜群です。
autogenerateという機能を備え、sqlalchemyで定義したschemaから自動でMigration scriptを作成できます。


## 基本的な使い方

### install


```bash
pip install alembic
```
(適宜使っているpackage管理ツールに置き換えてください)

### alembic環境構築
alembicはテンプレートの中から目的にあった設定ファイルをサクッと作ることができます。

```bash
$ alembic list_templates
Available templates:

generic - Generic single-database configuration.
multidb - Rudimentary multi-database configuration.
pylons - Configuration that reads from a Pylons project environment.

Templates are used via the 'init' command, e.g.:

  alembic init --template pylons ./scripts
```
https://alembic.sqlalchemy.org/en/latest/tutorial.html#creating-an-environment


### マイグレーションファイルの作成
基本的な作成方法は以下のコマンドを打って作成されるファイルにPythonまたはRawSQLでスクリプトを追加していくことです。

```bash
$ alembic revision -m "Add a column"
Generating /path/to/yourapp/alembic/versions/ae1027a6acf_add_a_column.py...
done
```

```python:ae1027a6acf_add_a_column.py
"""Add a column

Revision ID: ae1027a6acf
Revises: 1975ea83b712
Create Date: 2011-11-08 12:37:36.714947

"""

# revision identifiers, used by Alembic.
revision = 'ae1027a6acf'
down_revision = '1975ea83b712'

from alembic import op
import sqlalchemy as sa

def upgrade():
    # Migration適応内容記入
    # 例 op.add_column('account', sa.Column('last_transaction_date', sa.DateTime))

def downgrade():
    # rollback時適応内容記入
    # 例 op.drop_column('account', 'last_transaction_date')
```

しかしこのような使い方をしているのではalembicの恩恵を十分に受けられません。

alembic最大の強みはsqlalchemyで定義したモデルschemaを元に自動でMigration Scriptを生成してくれることです！

### Auto generate機能
https://alembic.sqlalchemy.org/en/latest/autogenerate.html

Auto generate機能を使うにはデフォルトで作成された`env.py`ファイルの中身を以下のように変更する必要があります。

```python:env.py(変更前)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None
```
```python:env.py(変更後)

from myapp.mymodel import Base (sqlalchemyで定義したモデルからdeclarative_baseをimport)
target_metadata = Base.metadata
```

変更後--autogenerateをつけるだけでmodel schemaを元に自動でMigration Scriptが作成されます！

```bash
$ alembic revision --autogenerate -m "Added account table"
INFO [alembic.context] Detected added table 'account'
Generating /path/to/foo/alembic/versions/27c6a30d7c24.py...done
```
```python:27c6a30d7c24.py
"""empty message

Revision ID: 27c6a30d7c24
Revises: None
Create Date: 2011-11-08 11:40:27.089406

"""

# revision identifiers, used by Alembic.
revision = '27c6a30d7c24'
down_revision = None

from alembic import op
import sqlalchemy as sa

def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.create_table(
    'account',
    sa.Column('id', sa.Integer()),
    sa.Column('name', sa.String(length=50), nullable=False),
    sa.Column('description', sa.VARCHAR(200)),
    sa.Column('last_transaction_date', sa.DateTime()),
    sa.PrimaryKeyConstraint('id')
    )
    ### end Alembic commands ###

def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.drop_table("account")
    ### end Alembic commands ###
```

Auto generate...強力ですね！
自分も最初使った時は思わず「alembic様、天才！」と声をあげそうになりました。

こうして作られたMigration scriptを実行していきましょう。



### マイグレーションの実行
```bash
alembic upgrade head
```
このコマンド一発でDBにMigration Scriptが実行されます。
DBを見てみると`alembic_version`と言うテーブルが作られており、`version_num`というカラムの中に最後に当てられたMigration fileのrevisionが保存されます。

この値を元にalembicはバージョン管理をし、rollbackを可能にしています。

rollbackをするには

```
alembic downgrade -1
```
 
で一つ前のmigrationを取り消すことができ、複数前のrevisionまでrollbackするには、

```
alembic history
```
でrevision情報を表示し、戻りたいrevisionを指定し

```
alembic downgrade {revision}
```

で遡ることができます。

以上が基本的な使い方ですが他の機能も紹介しておきたいと思います。

### tips
- [env切り替え方法](https://allan-simon.github.io/blog/posts/python-alembic-with-environment-variables/)

- [RawSQLを使う](http://allan-simon.github.io/blog/posts/alembic-use-sql-statement-instead-of-sqlalchemy/)
- [複数モデルの使用](https://qiita.com/orange634nty/items/dcbcc10eb14f5e10f398#%E8%A4%87%E6%95%B0%E3%81%AE%E3%83%A2%E3%83%87%E3%83%AB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%89%B1%E3%81%84%E3%81%9F%E3%81%84)

- [カラムのタイプ検知をenableにする方法](https://allan-simon.github.io/blog/posts/python-alembic-with-environment-variables/)


## メリット
メリットとしては以下の点があげられます。

- sqlalchemyで定義したschemaからmigration scriptを自動生成するauto generate機能

- 複数DB接続 / 環境によってDBを切り替えられる

- Python、Raw SQLの両方でMigration scriptをかける 

- Communityが大きい/SQLAlchemyの作者がContributeしていることから今後も機能拡張される

## デメリット

一番の欠点はautogenerateと言う素晴らしい機能を備えていますが、必ずしも万能ではないことです。

Auto generateで検知できない変更として以下の点があります。

- テーブル名の変更(drop/addになりデータが初期化されます)
- カラム名の変更(drop/addになりデータが初期化されます)
- 名前の付いていないユニーク制約 (変更を検知するにはユニーク制約に名前をつけることは必須(e.g. `UniqueConstraint('col1', 'col2', name="my_name")`)
- EnumなどのSQLAlchemy特有のカラムの変更
- Columnの順番の指定

[What does Autogenerate Detect (and what does it not detect?)](https://alembic.sqlalchemy.org/en/latest/autogenerate.html#what-does-autogenerate-detect-and-what-does-it-not-detect)

なので運用方法としては基本的にはauto generate、うまく生成されない場合は自分でマニュアルで記入する対応が必要になりそうです。
 


# simple-db-migrate
> simple-db-migrate is damn simple. The best way to understand how it works is by installing and using it.

https://github.com/guilhermechapiewski/simple-db-migrate

続いてsimple-db-migrateを試してみましょう。
simple-db-migrateはその名の通りめちゃめちゃシンプルなMigration Toolです。

## 基本的な使い方

### install

```bash
$ pip install simple-db-migrate
```

### 設定ファイルの作成

```bash
$ touch simple-db-migrate.conf
```
```:simple-db-migrate.conf
DATABASE_HOST = "localhost"
DATABASE_USER = "root"
DATABASE_PASSWORD = ""
DATABASE_NAME = "migration_example"
DATABASE_MIGRATIONS_DIR = "."
```

準備は上記のような設定ファイルを作成するだけです！
とってもSimple!

### マイグレーションファイルを作成する
```
$ db-migrate --create {Migration名(e.g. create_table_users)}
```

### マイグレーションを実行する
```
$ db-migrate
```
以上です！
簡単にマイグレーションを実行することができました。

実行するとDB内に`__db_version__`と言う名前のテーブルが作られversion管理されるようになっていきます。
--config, ---envオプションにより環境ごとに切り替えられ--migrationオプションにより指定のrevisionまでrollbackできます。


### tips
- [複数DB / env対応](https://github.com/guilhermechapiewski/simple-db-migrate#configuring-multiple-database-environments)


## メリット
- 複数DB / envに対応している
- simpleで導入が簡単

simple-db-migrationの一番のメリットはそのシンプルさ、導入の容易さですね！

## デメリット
- RawSQLしか使えない
- MySQLしか対応していない
- Auto generateのような機能はない

#まとめ

sqlalchemyと高い互換性を誇りautogenerateのような画期的な機能をもつalembicと、できる限りsimpleにバージョン管理を実現するsimple-db-migrationを比較してみました。

個人的にはalembicのauto generationがEnumのサポート、Column Orderの指定ができない点が少し残念に感じました。
autogenerateでなんでも変更検知されるはず、と思い込んでいると思わぬ事故を招きそうですね...

simple-db-migrationはその点、流すスクリプトもRawSQLでできることに制約はなく、最低限の機能は揃っているので実運用だとこちらに軍配があがる気がしました。（ただしMySQLを使用している場合に限りますが）


もしalembicのautogenerateの制約でワークアラウンド等ありましたら、コメント頂けるとありがたいです。（alembicのドキュメント量すごすぎて理解が追いついていない部分もあるかと思うので...）