python alembic
#alembic #python
はじめに
DBマイグレーションの知識を頭にぶち込んでおく。
Alembicのイメージとしては、Ruby on Railsのmigration機能と一緒。
というかDBマイグレーション機能と言ったら、どの言語使っても同じ感じになるはず。
ちなみに、Alembicは大抵SQLAlchemyと一緒に利用されることになってくる。
基本
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
...
注意点
Baseを利用してるモデルが記載されてるファイルもimportしてあげないと、そのモデルをalembicに認識されない
なんかalembicに認識させないとダメらしい
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にトランザクションが効かないものあるから気をつけて。
postgresqlはトランザクション効くけど、mysqlは無理。
Alembiがスキーマの全変更を自動検知することはできないと心得ておく
python alembic#64fef7cfb3641f0000f8b513
これに気づかずにマイグレーションを実行しちゃったら大変よ..
テーブルロックを引き起こす移行は避ける
レコードロックならまだしもテーブルロックはやばい。
排他ロックとかなら目も当てられない。読み込みすらできなくなるでしょう。
レコードでそれするならまだいいけど、テーブルでやると影響がでかいので注意。
その移行が妥当かどうかをチェックするリンターを使う
squawkっていうツールが有名らしいので確かめてみ
継続的インテグレーション (CI) パイプラインに移行を検証するステップを追加すると、Alembic の移行に関するエラーや問題を本番環境にデプロイする前に検出するのに役立ちます。
Rules Overview | Squawk — a linter for Postgres migrations
使い方:
Web Frameworks | Squawk — a linter for Postgres migrations
install:
Quick Start | Squawk — a linter for Postgres migrations
これpostgresql用のやつなので気をつけて。
移行しても下位互換性を維持する
データベースのスキーマを変更した後、アプリケーションコードと食い違いが発生するのを防がなくてはならない。
アプリケーションのコードは前のスキーマ前提で動いてる
そのことを常に意識して移行を進めること。
具体例として移行時には以下のことに気をつける。
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 一部検知を変更してくれないものもあるらしいで、気をつけて
Auto Generating Migrations — Alembic 1.12.0 documentation
/PythonOsaka/Alembicでマイグレーションをしてみよう#5ea64903d67bf90000e3d436
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を出力する関数
【FastAPI+MySQL】alembicでマイグレーションファイルを自動生成して実行する
run_migrations_online()が通常のマイグレーション実行時に使われる関数で、run_migrations_offline()は--sqlオプションをつけたときに使われる関数です。
hr.icon
Input
DBマイグレーションツールのAlembicの使い方
PythonのRDBマイグレーションツール「Alembic」を使ってみる | Sqripts
Base.metadata.create_all(engine)
この行はModel定義に従ってテーブルを作成するコードでした。今回はその役割をAlembicが担うことになるのでこの行は不要となります。
そうかぁ、確かにalembicがあったら勝手に作ってくれるよな。
わざわざ自前でスクリプト作らなくてもいいよな。
/PythonOsaka/Alembicでマイグレーションをしてみよう
/PythonOsaka/Alembicでマイグレーションをしてみよう#5ea65efcd67bf90000e3d488
データベースによっては、マイグレーションのup/downが効かない場合もあるとonigiri.w2.icon
そういう場合は、自分でマイグレーションファイルをいい感じに編集しないとダメそうだ。こわやこわやonigiri.w2.icon
/PythonOsaka/Alembicでマイグレーションをしてみよう#5ea64903d67bf90000e3d436
alembicが変更を自動検出できるパターンは全てじゃないとonigiri.w2.icon
テーブル名の変更
カラム名の変更
名前の付いていないユニーク制約
データベースが直接サポートしていない特殊な SQLAlchemy の型を使う場合
geometryいけるか?ww
無理なんじゃないか?大丈夫かな。
Alembic導入
alembicはデータベースにalembic_versionというテーブルを作成し、そこにマイグレーションの実行履歴を記録しているため手動で履歴を追加する
マイグレーションファイルだけじゃないのね。どのタイミングでテーブルに作られる?
多分、upgrade head 実行した時じゃないかと予想してるけど
なぜかというとReactionHistoryにカラムを追加してalembic revision --autogenerateを実行すると、生成されたマイグレーションファイルのupgradeでテーブルがまるまる削除されてしまう問題が発生したから
こんなん起きられたらたまったもんじゃないでぇ...onigiri.w2.icon
絶対にマイグレーションファイル作った時は慎重に慎重に確認しないと...
Alembic Migrations Without Downtime | Exness Tech Blog