python alembic
はじめに
Alembicのイメージとしては、Ruby on Railsのmigration機能と一緒。
というかDBマイグレーション機能と言ったら、どの言語使っても同じ感じになるはず。
基本
hr.icon
1. alembicをインストール
$ pip install alembic
2. プロジェクトのルートディレクトリにて、初期化コマンド実行
$ alembic init migrations
3. この時生成されるファイルたち
code: dir
- migrations/
- versions/
- env.py
- script.py.mako
- alembic.ini
各ファイルの詳細については参考資料を確認せよ
4. SQLAlchemy側でモデル定義する
code: settings.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:///test.db')
Base = declarative_base()
code: models.py
from sqlalchemy import Column, Integer, String
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
nickname = Column(String)
5. env.pyからBaseと各モデル定義ファイルをimportして利用
code: env.py
...
from src.settings import Base
from src.models import User
target_metadata = Base.metadata
...
注意点
6. sqlalchemyの利用してるDBのURLをalembicに認識させる
方法は2つ
1.icon alembic.iniに記載する
2.icon env.pyにて値を代入する
1.iconの方法
code: alembic.ini
sqlalchemy.url = sqlite:///test.db
こんな感じで、SQLAlchemyが指してるDBのURLを指定する
2.iconの方法
code: env.py
from alembic import context
...
config = context.config
config.set_main_option('sqlalchemy.url', 'sqlite:////test.db')
...
7. migrationファイルを作成
$ alembic revision --autogenerate -m "Initial"
revisionというサブコマンドでファイル生成してる
-mはコミットメッセージ的なもの。
--autogenerateは、Baseに紐づいてるモデルを自動で読み解いて、マイグレーションファイル内のコードを自動記載してくれるオプション。
これないと、自分で中身記載することになる。
8. migrate実行
$ alembic upgrade head
その他の基本
hr.icon
今のDBのマイグレーション反映状況を確認するには...
$ alembic current
今までのマイグレーションの履歴を見るには
$ alembic history
ダウングレードするには
$ almebic downgrade -1
$ almebic downgrade <revision>
マイグレーションファイルをマージもできるよ
$ alembic merge
利用する上でのベストプラクティス
hr.icon
オンライン移行をする場合は、長いトランザクションを要する移行はしないこと
オンラインなので、ユーザーも引き続き使ってる状況。
その上で長いトランザクションを実施してDBをロックしてしまったり、リソースを大量に使うとユーザーの利用に影響出る可能性ある。
大きな移行が必要な場合は、段階的に移行することを考えた方がいい。
もしくは「メンテナンスです」と言ってしまい、オフラインで実施するとか。
alembicの場合、transaction_per_migration=Trueていう手がある。
よくあるユースケース: 「開発の中で複数のマイグレーションファイルが作成されて、それらの移行を本番DBに反映する」
transaction_per_migrationはデフォルトがFalseであり、もし上記のようなことをすると、、、
1つのトランザクションの中で全てのマイグレーションファイルが実行されることになる
とても大きなトランザクションになる。
そういうのを避けるためにtransaction_per_migration=Trueにして、1つのマイグレーションファイルごとにトランザクションを実施してく方がいい。
あとは開発の中で1つだけマイグレーションファイルを作ったが、その内容があまりにも大きいので複数のマイグレーションファイルに分割するってやり方もあるよね。
注意.icon データベースによってはDDLにトランザクションが効かないものあるから気をつけて。
Alembiがスキーマの全変更を自動検知することはできないと心得ておく
これに気づかずにマイグレーションを実行しちゃったら大変よ..
テーブルロックを引き起こす移行は避ける
レコードロックならまだしもテーブルロックはやばい。
排他ロックとかなら目も当てられない。読み込みすらできなくなるでしょう。
レコードでそれするならまだいいけど、テーブルでやると影響がでかいので注意。
その移行が妥当かどうかをチェックするリンターを使う
継続的インテグレーション (CI) パイプラインに移行を検証するステップを追加すると、Alembic の移行に関するエラーや問題を本番環境にデプロイする前に検出するのに役立ちます。
使い方:
install:
移行しても下位互換性を維持する
データベースのスキーマを変更した後、アプリケーションコードと食い違いが発生するのを防がなくてはならない。
そのことを常に意識して移行を進めること。
具体例として移行時には以下のことに気をつける。
1. 新しいフィールドの追加
新しいフィールドがNOT NULL制約あったらエラー起きるだろう。
例え、新しいフィールドがNOT NULL制約が必要だとしても、移行時にエラー起きるので入れてはいけない。
コード側を変更してから、NOT NULL制約を入れるといいでしょう。
また、コード側がフィールドの数とかでロジック組んでても終わり。。これはコード側でも気をつけたいな。
2. フィールドの削除
これもすぐ削除したらどうなるかわかるよね。
コード側が、そのフィールドがある前提で動くからね。エラー起きる。
移行時はコード側から先に変更するとかの対応が必要だね。
その後にフィールドを削除するみたいな。
3. フィールド名の変更
これはもう難しすぎる。難題だわ。
フィールド名変更した瞬間、コード側でエラーが起きるだろう。
これをやりたかったら...
変更先の名前となるフィールドを新しく作成
新しいフィールドにもデータをコピー
コード側を変更
古いフィールドを削除
マイグレーションを適用する前に、現在のDBのリビジョンが最新かどうか確認しておけい
思いもよらぬ事態が起きたりするよ。気をつけて。
例えば、本番と環境のDBのリビジョンが異なっており、それゆえにテスト環境とは違うことが起きた!てなことも...
自動作成されたマイグレーションファイルの中身は必ず確認!!!!! これを怠ると死が待っているぞよ
予想だにしない変更してたりする可能性あるから、ちゃんとチェック。
(事例を知らんからあれやけど)alembicでスキーマを変えることはしても、データも一緒に移行するのは避けた方がいいかもしれない?
データを別のテーブルに移したい時とかあるやん。
そういう時、マイグレーションのDDL文と一緒にDML文も実行しちゃいたいよね。これってありなんかなぁ。
コピーくらいなら不整合起きないと思うんだけど。
以下あたりがリスクかなぁ
1. データコピーをマイグレーション時にしちゃうと、大量にDBのリソース使うことになって、オンライン移行ならユーザー影響でる。
2. ドメインロジックを通さないデータ移動は、不整合が起きる可能性が大いにある。
3. 多分...データ移行も入っちゃうと、マイグレーションの運用が後々ややこしくなるんじゃないか説ある。
データ移行したいなら、alembicではなく自前で整合性チェックしつつデータ移行した方がいいかなぁと思いますonigiri.w2.icon
よくやらかすミスを事前把握
マイグレーションファイル作成後、DBに反映しないまま、テーブル定義を変更、そしてマイグレーションファイルを再作成しない。
これやると終わる。
次は、逆にマイグレーションスクリプトを生成し忘れる。
データスキーマ変わってると思って、コード側変更したら...orz
こういうミスを防ぎたいならalembic checkを逐一使ったり、デプロイ前のCIで使ったりして防止しようね!!!
質問
hr.icon
q.icon asyncにはどうやって対応するの?
a.icon 別にasyncに対応する必要はないと思う。同期的にデータスキーマを変更すればよろしいかとonigiri.w2.icon
なんでasyncは気にする必要なし。
q.icon alembic.iniのsqlalchemy.urlの値を環境変数によって振り分けたい。どうする?
a.icon env.pyを変更したらいけそう
code: env.py
config.set_main_option( "sqlalchemy.url", os.getenv('...'))
q.icon 例えば、TableAにあるカラムのデータを新しいTableBのあるカラムに丸ごと移すことってできる?
a.icon alembicの機能だけでやるのは危険すぎる。順序立てて移行を進めた方がいい。
オンラインでやるなら、以下のような感じ
1. 新しくカラムを追加する(NOT NULL制約は無し
2. コードを新しいカラムに向くように変更
3. 古いカラムを削除する
これは一例であり、状況によってはやり方を変えた方がいいこともある。
q.icon 複数のmodelがあるときは、env.pyにそいつら全部インポートする必要ある?
a.icon 検証したら、別に無くてもいいかも。
target_metadata = Base.metadataだけをenv.pyに入れておけばいいよ。
いえ、やっぱり必要です。適宜モデルはimportしてください。じゃないとalembicが認識しないことがあります。
q.icon 生成されたmigrationファイルをこっちで修正する時ってどんな時なん?
a.icon 以下あたりかなぁ
Alembicが自動検知しない変更があって、それを含めたい時
downgrade関数がやばそうな時
q.icon 一部検知を変更してくれないものもあるらしいで、気をつけて
q.icon 2つ以上前のバージョンにダウンリビジョンする時ってどういう挙動してるの?1つ1つダウングレードしてる?
a.icon YES
履歴をちゃんと見ないと正しく、そのバージョンに戻れない。
変更の蓄積やから。
q.icon データベースにマイグレーションのリビジョン?が登録されるっぽいがどのタイミングだろうか
a.icon alembic upgrade headが実施された時っすね。
q.icon offlineとonlineの2つほどありそうだが、そこらへんちゃんと知りたいな
onlineは実際にDBに変更を加える関数
offlineはDBに変更を加える際のSQLを出力する関数
run_migrations_online()が通常のマイグレーション実行時に使われる関数で、run_migrations_offline()は--sqlオプションをつけたときに使われる関数です。
hr.icon
Input
Base.metadata.create_all(engine)
この行はModel定義に従ってテーブルを作成するコードでした。今回はその役割をAlembicが担うことになるのでこの行は不要となります。
そうかぁ、確かにalembicがあったら勝手に作ってくれるよな。
わざわざ自前でスクリプト作らなくてもいいよな。
データベースによっては、マイグレーションのup/downが効かない場合もあるとonigiri.w2.icon
そういう場合は、自分でマイグレーションファイルをいい感じに編集しないとダメそうだ。こわやこわやonigiri.w2.icon
alembicが変更を自動検出できるパターンは全てじゃないとonigiri.w2.icon
テーブル名の変更
カラム名の変更
名前の付いていないユニーク制約
データベースが直接サポートしていない特殊な SQLAlchemy の型を使う場合
geometryいけるか?ww
無理なんじゃないか?大丈夫かな。
alembicはデータベースにalembic_versionというテーブルを作成し、そこにマイグレーションの実行履歴を記録しているため手動で履歴を追加する
マイグレーションファイルだけじゃないのね。どのタイミングでテーブルに作られる?
多分、upgrade head 実行した時じゃないかと予想してるけど
なぜかというとReactionHistoryにカラムを追加してalembic revision --autogenerateを実行すると、生成されたマイグレーションファイルのupgradeでテーブルがまるまる削除されてしまう問題が発生したから
こんなん起きられたらたまったもんじゃないでぇ...onigiri.w2.icon
絶対にマイグレーションファイル作った時は慎重に慎重に確認しないと...