【SQLAlchemy】Alembicチュートリアル:DBスキーマ変更を簡単管理


SQLAlchemy Alembicチュートリアル:データベーススキーマ変更をマスターする徹底ガイド

導入

現代のWebアプリケーション開発において、データベースはアプリケーションの心臓部とも言える重要な役割を担っています。開発が進むにつれて、新しい機能の追加や仕様変更に伴い、データベースのテーブル構造(スキーマ)を変更する必要が頻繁に発生します。

しかし、このスキーマ変更の管理は、しばしば開発者を悩ませる厄介な問題となります。手動でSQL文を書いてデータベースに直接適用する方法は、一見シンプルに見えますが、多くのリスクを伴います。例えば、以下のような課題が挙げられます。

  • ヒューマンエラー: SQLの書き間違いにより、データを損失したり、意図しない変更を加えてしまったりする可能性があります。
  • バージョン管理の困難さ: 「いつ」「誰が」「どのような目的で」スキーマを変更したのか、その履歴を追うのが困難になります。Gitでアプリケーションコードを管理していても、データベーススキーマが追随していなければ、過去のバージョンに戻すことはできません。
  • 環境間の差異: 開発環境、ステージング環境、本番環境でスキーマのバージョンが異なってしまい、環境固有のバグを引き起こす原因となります。
  • チーム開発の非効率化: 複数の開発者がそれぞれスキーマを変更すると、コンフリクトが発生し、誰の変更を適用すべきか調整する手間がかかります。

これらの問題を解決するために登場したのが「データベースマイグレーションツール」です。そして、PythonのORM(Object-Relational Mapper)として絶大な人気を誇るSQLAlchemyエコシステムにおいて、その役割を担うのがAlembicです。

Alembicは、データベーススキーマの変更をPythonコードとして管理し、バージョン付けを可能にする軽量かつ強力なツールです。Alembicを導入することで、スキーマ変更のプロセスは安全で、再現性が高く、そして何より効率的になります。

この記事では、Alembicを初めて使う方を対象に、その基本概念から実践的な応用までをステップバイステップで徹底的に解説します。環境構築から始め、マイグレーションの作成、適用、そしてチーム開発における高度な使い方まで、この一本の記事でマスターできることを目指します。さあ、Alembicと共に、煩雑なスキーマ管理から解放され、より本質的な開発に集中しましょう。

第1章: Alembicとは?なぜ必要か?

Alembicの具体的な使い方に入る前に、まずは「Alembicとは何か」「なぜそれを使うべきなのか」をしっかりと理解しておきましょう。

1-1. Alembicの概要

Alembicは、SQLAlchemyの作者であるMike Bayer氏によって開発された、データベースマイグレーションツールです。SQLAlchemyと密に連携するように設計されており、SQLAlchemyで定義されたモデル(テーブルの構造を表現するPythonクラス)からスキーマの変更を自動的に検出し、マイグレーションスクリプトを生成する強力な機能を持っています。

Alembicの主な機能は以下の通りです。

  • リビジョン管理: スキーマの各変更を「リビジョン」という単位で管理します。各リビジョンは一意のIDを持ち、変更履歴が時系列で保存されます。
  • アップグレードとダウングレード: 特定のリビジョンへスキーマを進める(アップグレード)ことや、前のリビジョンへ戻す(ダウングレード)ことが可能です。これにより、安全なロールバックが実現します。
  • スクリプトの自動生成: SQLAlchemyのモデル定義と現在のデータベースの状態を比較し、差分を検出してマイグレーションスクリプトの雛形を自動で生成します(autogenerate機能)。
  • ブランチ管理: 複数の開発者が並行して異なるスキーマ変更を行った場合でも、それらの変更を後から統合(マージ)するためのブランチとマージの仕組みを提供します。
  • プレーンSQLのサポート: ORMを通さず、生のSQL文をマイグレーションスクリプトに記述することもできます。これにより、複雑なデータ移行やパフォーマンスが要求される操作にも柔軟に対応できます。

他のフレームワーク、例えばDjangoには独自のマイグレーションシステムが組み込まれていますが、Alembicは特定のWebフレームワークに依存しないため、FastAPI, Flask, Pyramidなど、SQLAlchemyを使用するあらゆるPythonアプリケーションで利用できる汎用性の高さが特徴です。

1-2. Alembicを使うメリット

Alembicを導入することで、開発プロセスに計り知れないメリットがもたらされます。

  • 変更履歴のコード化とバージョン管理:
    スキーマの変更がすべてPythonファイルとしてversionsディレクトリに保存されます。これにより、Gitなどのバージョン管理システムでアプリケーションコードと一緒にスキーマの変更履歴も一元管理できます。「このコミットではどのスキーマ変更が行われたか」が一目瞭然となり、コードレビューの対象にもなります。

  • 再現性の確保:
    新しい開発者がプロジェクトに参加した際や、新しいサーバーを構築する際に、alembic upgrade headというコマンド一発で、データベースを最新のスキーマ状態に構築できます。手作業によるセットアップミスがなくなり、どの環境でも全く同じデータベーススキーマを再現できます。

  • 安全なデプロイとロールバック:
    本番環境へのデプロイ時に、まずマイグレーションを適用します。もしデプロイ後に問題が発生した場合でも、alembic downgradeコマンドを使えば、スキーマを問題が発生する前の状態に安全に戻すことができます。これにより、障害からの復旧時間が大幅に短縮されます。

  • autogenerateによる開発効率の向上:
    SQLAlchemyでモデル定義を変更した後、alembic revision --autogenerateコマンドを実行するだけで、カラムの追加・削除・型変更などを自動で検出し、マイグレーションスクリプトを生成してくれます。これにより、面倒な定型SQLを手で書く手間が省け、開発者はビジネスロジックの実装に集中できます。

  • チーム開発の円滑化:
    チームで開発していると、Aさんがユーザー機能、Bさんが商品機能で、それぞれデータベーススキーマを変更する、といった状況が頻繁に起こります。Alembicを使えば、それぞれの変更がリビジョンとして記録され、後から安全に統合できます。これにより、スキーマ変更のコンフリクトを未然に防ぎ、スムーズな共同作業を実現します。

このように、Alembicは単なる便利ツールではなく、現代のアプリケーション開発における「当たり前のプラクティス」を支える基盤技術と言えるでしょう。

第2章: 環境構築とAlembicの初期設定

それでは、実際にAlembicを使ってみましょう。ここでは、シンプルなプロジェクトを例に、環境構築からAlembicの初期設定までを行います。

2-1. プロジェクトの準備

まず、プロジェクト用のディレクトリを作成し、Pythonの仮想環境を構築します。仮想環境を使うことで、プロジェクトごとにライブラリのバージョンを分離でき、環境の汚染を防ぎます。

“`bash

プロジェクトディレクトリを作成して移動

mkdir alembic_tutorial
cd alembic_tutorial

Python仮想環境を作成

python3 -m venv venv

仮想環境を有効化 (macOS/Linux)

source venv/bin/activate

(Windowsの場合)

venv\Scripts\activate

“`

次に、必要なライブラリをインストールします。SQLAlchemy本体、Alembic、そしてデータベースに接続するためのドライバが必要です。ここでは、広く使われているPostgreSQLを例に進めますが、MySQLやSQLiteなど、SQLAlchemyがサポートする他のデータベースでも手順はほぼ同じです。

“`bash

必要なライブラリをインストール

psycopg2-binaryはPostgreSQL用のドライバです

pip install sqlalchemy alembic psycopg2-binary
“`

インストールが完了したら、後で他の環境でも同じライブラリをインストールできるように、requirements.txtを作成しておきましょう。

bash
pip freeze > requirements.txt

2-2. Alembicプロジェクトの初期化

ライブラリの準備ができたら、alembicコマンドを使ってプロジェクトを初期化します。

bash
alembic init alembic

このコマンドを実行すると、カレントディレクトリにalembicというディレクトリとalembic.iniというファイルが生成されます。これがAlembicの心臓部です。

生成されたファイルとディレクトリの構造を見てみましょう。

alembic_tutorial/
├── alembic/
│ ├── versions/ # マイグレーションスクリプトが格納される場所(最初は空)
│ ├── env.py # Alembicの実行環境を設定するスクリプト
│ ├── script.py.mako # マイグレーションスクリプトのテンプレート
│ └── README
├── alembic.ini # Alembicのメイン設定ファイル
├── venv/
└── requirements.txt

それぞれの役割を簡単に説明します。

  • alembic.ini: Alembicの全体的な設定を行うINI形式のファイルです。データベースへの接続情報などをここに記述します。
  • alembic/: Alembic関連のスクリプトや設定が格納されるメインディレクトリです。
  • alembic/env.py: Alembicがマイグレーションを実行する際に読み込まれるPythonスクリプトです。データベース接続の確立や、SQLAlchemyモデルの読み込み設定など、実行環境に関する重要な設定を行います。
  • alembic/versions/: autogenerate機能などで生成されたマイグレーションスクリプト(リビジョンファイル)が、ここにpyファイルとして保存されていきます。
  • alembic/script.py.mako: 新しいマイグレーションスクリプトを生成する際のテンプレートファイルです。Makoというテンプレートエンジンが使われています。

2-3. alembic.ini の設定

まず、alembic.iniを編集して、データベースへの接続情報を設定します。ファイルを開き、[alembic]セクションにあるsqlalchemy.urlの行を見つけてください。

“`ini

alembic.ini

…(省略)…

A generic, single database configuration.

[alembic]

path to migration scripts

script_location = alembic

…(省略)…

SQLAlchemy database URL.

(コメントアウトを外して、自分のDB接続情報に書き換える)

sqlalchemy.url = postgresql+psycopg2://user:password@host:port/dbname

…(省略)…

“`

sqlalchemy.urlの行の先頭にある#を削除してコメントアウトを解除し、ご自身のPostgreSQLの接続情報に書き換えてください。例えば、ユーザー名がmyuser、パスワードがmypass、ホストがlocalhost、データベース名がmydbの場合は以下のようになります。

ini
sqlalchemy.url = postgresql+psycopg2://myuser:mypass@localhost/mydb

注意: パスワードなどの機密情報を設定ファイルに直接書き込むのはセキュリティ上好ましくありません。実際のプロジェクトでは、環境変数から読み込むようにenv.pyをカスタマイズすることが推奨されます。

2-4. env.py の設定

次に、alembic/env.pyを編集します。このファイルは、Alembicがautogenerate機能を使う際に、どのSQLAlchemyモデルを監視すればよいかを教えるために非常に重要です。

まず、プロジェクトのモデルを定義するファイルを作成しましょう。プロジェクトのルートディレクトリ(alembic_tutorial/)にmodels.pyという名前でファイルを作成します。

“`python

models.py

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
tablename = ‘users’
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
email = Column(String(100), unique=True)
“`

このmodels.pyで定義したBase.metadataをAlembicに認識させる必要があります。alembic/env.pyを開き、以下の2箇所を編集します。

  1. モデルのインポート:
    ファイルの先頭あたりに、作成したmodels.pyからBaseオブジェクトをインポートするコードを追加します。

    “`python

    alembic/env.py

    from logging.config import fileConfig

    from sqlalchemy import engine_from_config
    from sqlalchemy import pool

    from alembic import context

    この行を追加

    from models import Base

    …(省略)…

    “`

  2. target_metadataの設定:
    ファイルの中程にtarget_metadata = Noneという行があります。ここを、先ほどインポートしたBase.metadataに書き換えます。これにより、Alembicはこのmetadataオブジェクトに含まれるテーブル定義を監視対象とします。

    “`python

    alembic/env.py

    …(省略)…

    add your model’s MetaData object here

    for ‘autogenerate’ support

    from myapp import mymodel

    target_metadata = mymodel.Base.metadata

    ↓↓↓ このように書き換える ↓↓↓

    target_metadata = Base.metadata

    …(省略)…

    “`

env.pyはプロジェクトのルートディレクトリから実行されるため、from models import Baseという直接的なインポートが機能します。もしモデルがパッケージ内にある場合は、sys.pathを追加するなどの調整が必要になることもあります。

以上で、Alembicを使うための基本的な設定は完了です。次の章では、いよいよ最初のマイグレーションを作成していきます。

第3章: 最初のマイグレーション

設定が完了したところで、Alembicのコア機能であるマイグレーションのライフサイクル(生成→適用→取り消し)を体験してみましょう。

3-1. SQLAlchemyモデルの定義(再確認)

第2章で、以下のようなシンプルなUserモデルをmodels.pyに定義しました。このモデルが、これから作成するテーブルの設計図となります。

“`python

models.py

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
tablename = ‘users’
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
email = Column(String(100), unique=True)
“`

この時点では、データベースにはまだusersテーブルは存在しません。Alembicを使って、このモデルに対応するテーブルを作成するマイグレーションを行います。

3-2. マイグレーションスクリプトの自動生成 (autogenerate)

Alembicの最も強力な機能の一つがautogenerateです。これは、env.pyで設定したtarget_metadata(つまりmodels.pyのモデル定義)と、現在のデータベースのスキーマ状態を比較し、その差分からマイグレーションスクリプトを自動生成する機能です。

ターミナルで以下のコマンドを実行してください。

bash
alembic revision --autogenerate -m "Create users table"

  • alembic revision: 新しいリビジョンファイルを作成するコマンドです。
  • --autogenerate: スキーマの差分を自動検出するよう指示します。
  • -m "Create users table": このリビジョンが何を行う変更なのかを説明するメッセージです。ファイル名やコメントに使われ、後から履歴を見返す際に非常に役立ちます。

コマンドが成功すると、以下のような出力が表示され、alembic/versions/ディレクトリに新しいPythonファイルが作成されます。

INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'users'
Generating /path/to/alembic_tutorial/alembic/versions/xxxxxxxxxxxx_create_users_table.py ... done

xxxxxxxxxxxxの部分は実行ごとに生成される一意のリビジョンIDです。生成されたファイル(例: d48a637a2e8c_create_users_table.py)の中身を見てみましょう。

“`python

alembic/versions/d48a637a2e8c_create_users_table.py

“””Create users table

Revision ID: d48a637a2e8c
Revises:
Create Date: 2023-10-28 12:00:00.000000

“””
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa

revision identifiers, used by Alembic.

revision: str = ‘d48a637a2e8c’
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

def upgrade() -> None:
# ### commands auto generated by Alembic – please adjust! ###
op.create_table(‘users’,
sa.Column(‘id’, sa.Integer(), nullable=False),
sa.Column(‘name’, sa.String(length=50), nullable=False),
sa.Column(‘email’, sa.String(length=100), nullable=True),
sa.PrimaryKeyConstraint(‘id’),
sa.UniqueConstraint(‘email’)
)
# ### end Alembic commands ###

def downgrade() -> None:
# ### commands auto generated by Alembic – please adjust! ###
op.drop_table(‘users’)
# ### end Alembic commands ###
“`

このスクリプトの重要なポイントを解説します。

  • revision: このリビジョンの一意なIDです。
  • down_revision: このリビジョンの「親」となるリビジョンのIDです。今回は最初のマイグレーションなのでNoneになっています。Alembicはこの情報を使ってマイグレーションの順序を管理します。
  • upgrade()関数: このリビジョンを適用する(スキーマを進める)際の処理が書かれています。op.create_table()は、SQLAlchemyのTableオブジェクトの定義に非常に似た形で、usersテーブルを作成する操作を表現しています。
  • downgrade()関数: このリビジョンを取り消す(スキーマを戻す)際の処理が書かれています。upgrade()の逆の操作、つまりop.drop_table()が定義されています。

autogenerateはあくまで雛形を生成する機能です。生成されたスクリプトは必ず目視で確認し、意図通りの内容になっているか、必要に応じて手動で修正を加えることが重要です。

3-3. データベースへの適用 (upgrade)

マイグレーションスクリプトができたので、これをデータベースに適用して、実際にテーブルを作成しましょう。以下のコマンドを実行します。

bash
alembic upgrade head

  • alembic upgrade: スキーマをアップグレードするコマンドです。
  • head: 「最新の」リビジョンを指すエイリアスです。これにより、利用可能なすべてのマイグレーションが最新の状態まで適用されます。特定のバージョンまで適用したい場合は、リビジョンIDを指定することもできます(例: alembic upgrade d48a637a2e8c)。

コマンドが成功すると、upgrade()関数に書かれたCREATE TABLE文がデータベースに対して実行されます。

ここで、データベースの中身を確認してみましょう。PostgreSQLのクライアントpsqlなどを使って接続します。

“`bash

psql -U myuser -d mydb

テーブル一覧を表示

mydb=> \dt
List of relations
Schema | Name | Type | Owner
——–+——————-+——-+———-
public | alembic_version | table | myuser
public | users | table | myuser
(2 rows)

usersテーブルの構造を確認

mydb=> \d users
Table “public.users”
Column | Type | Collation | Nullable | Default
——–+————————+———–+———-+———————————–
id | integer | | not null | nextval(‘users_id_seq’::regclass)
name | character varying(50) | | not null |
email | character varying(100) | | |
Indexes:
“users_pkey” PRIMARY KEY, btree (id)
“users_email_key” UNIQUE CONSTRAINT, btree (email)
“`

usersテーブルがモデル定義通りに作成されていることが確認できます。

もう一つ注目すべきは、alembic_versionというテーブルが自動的に作成されていることです。このテーブルの中身を見てみましょう。

“`bash
mydb=> SELECT * FROM alembic_version;
version_num


d48a637a2e8c
(1 row)
“`

このテーブルには、現在データベースに適用されている最新のリビジョンIDが記録されています。Alembicは、このテーブルの情報とversionsディレクトリ内のリビジョンファイルを比較することで、次にどのマイグレーションを適用すべきかを判断します。

3-4. マイグレーションの取り消し (downgrade)

次に、適用したマイグレーションを取り消してみましょう。downgradeコマンドを使用します。

bash
alembic downgrade -1

  • alembic downgrade: スキーマをダウングレードするコマンドです。
  • -1: 現在のバージョンから1つ前のリビジョンに戻す、という意味です。baseを指定すると、すべてのマイグレーションが適用される前の初期状態に戻ります。

このコマンドは、リビジョンd48a637a2e8cdowngrade()関数を実行します。つまり、usersテーブルを削除(DROP TABLE)します。

再度データベースを確認すると、usersテーブルが消え、alembic_versionテーブルのレコードも削除されている(またはdown_revisionの値に更新される)ことがわかります。

“`bash
mydb=> \dt
List of relations
Schema | Name | Type | Owner
——–+——————-+——-+———-
public | alembic_version | table | myuser
(1 row)

mydb=> SELECT * FROM alembic_version;
version_num


(0 rows)
“`

このように、upgradedowngradeを使いこなすことで、データベースのスキーマバージョンを自由に行き来できるようになります。実験が終わったら、再度alembic upgrade headを実行して、usersテーブルを最新の状態に戻しておきましょう。

第4章: 実践的なスキーマ変更

テーブルの新規作成ができるようになったので、次はより実践的なスキーマ変更、例えばカラムの追加や変更、データ移行などをAlembicでどう扱うかを見ていきましょう。

4-1. カラムの追加

Userモデルに、ユーザーが作成された日時を記録するcreated_atカラムを追加してみましょう。models.pyを以下のように修正します。

“`python

models.py

from sqlalchemy import … の行に

DateTime, func を追加

from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.sql import func
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
tablename = ‘users’
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
email = Column(String(100), unique=True)
# 以下の行を追加
created_at = Column(DateTime, server_default=func.now())
“`

server_default=func.now()は、データベース側でレコード挿入時に自動的に現在時刻をセットするための設定です。

モデルを変更したので、autogenerateでマイグレーションスクリプトを生成します。

bash
alembic revision --autogenerate -m "Add created_at to users table"

生成された新しいリビジョンファイル(例: 1b2c3d4e5f6g_add_created_at_to_users_table.py)の中身を確認します。

“`python

def upgrade() -> None:
# ### commands auto generated by Alembic – please adjust! ###
op.add_column(‘users’, sa.Column(‘created_at’, sa.DateTime(), server_default=sa.text(‘now()’), nullable=True))
# ### end Alembic commands ###

def downgrade() -> None:
# ### commands auto generated by Alembic – please adjust! ###
op.drop_column(‘users’, ‘created_at’)
# ### end Alembic commands ###
“`

upgrade()ではop.add_column()が、downgrade()ではop.drop_column()が生成されており、意図通りの内容になっていることがわかります。

alembic upgrade headを実行して、変更をデータベースに適用しましょう。適用後、\d usersでテーブル定義を確認すると、created_atカラムが追加されていることがわかります。

4-2. カラムの変更(型、制約など)

次に、既存のカラムの属性を変更してみましょう。Userモデルのemailカラムは、現在unique=Trueですが、NULLを許容しています。これを非NULL(nullable=False)にしてみます。

models.pyを修正します。

“`python

models.py

class User(Base):
# …
# emailカラムの nullable を False に変更
email = Column(String(100), unique=True, nullable=False)
# …
“`

再度autogenerateを実行します。

bash
alembic revision --autogenerate -m "Make users.email non-nullable"

生成されたスクリプトを見てみましょう。

“`python

def upgrade() -> None:
# ### commands auto generated by Alembic – please adjust! ###
op.alter_column(‘users’, ‘email’,
existing_type=sa.VARCHAR(length=100),
nullable=False)
# ### end Alembic commands ###

def downgrade() -> None:
# ### commands auto generated by Alembic – please adjust! ###
op.alter_column(‘users’, ‘email’,
existing_type=sa.VARCHAR(length=100),
nullable=True)
# ### end Alembic commands ###
“`

op.alter_column()が使われ、nullable属性が変更されていることがわかります。このように、カラムの型、NULL可能性、ユニーク制約などの一般的な変更はautogenerateで自動検出できます。

注意: autogenerateが全ての変更を検出できるわけではありません。例えば、server_defaultの変更や、CHECK制約の追加・削除などは、データベースバックエンドによる違いも大きく、自動検出が難しい場合があります。そのような場合は、空のリビジョンを作成し、手動でop.alter_columnなどを記述する必要があります。

4-3. テーブルやカラムのリネーム

テーブル名やカラム名を変更したい場合はどうでしょうか。これはautogenerateにとって少し難しいタスクです。なぜなら、Alembicから見ると「古い名前のテーブルが消え、新しい名前のテーブルが追加された」ように見え、リネームであると判断できないからです。

このような場合は、手動でマイグレーションスクリプトを作成します。

まず、alembic revisionコマンドで、--autogenerateを付けずに空のリビジョンファイルを作成します。

bash
alembic revision -m "Rename users table to app_users"

これにより、upgrade()downgrade()が空のマイグレーションスクリプトが生成されます。このファイルを編集し、op.rename_table()を使ってリネーム処理を記述します。

“`python

def upgrade() -> None:
op.rename_table(‘users’, ‘app_users’)

def downgrade() -> None:
op.rename_table(‘app_users’, ‘users’)
“`

カラムのリネームも同様に、op.alter_columnnew_column_name引数を使って手動で記述します。

4-4. データマイグレーション

スキーマの変更だけでなく、既存のデータを移行・変換したい場合もあります。これを「データマイグレーション」と呼びます。

例えば、Userモデルのnameカラムをfirst_namelast_nameに分割するシナリオを考えます。

  1. first_namelast_nameカラムをUserモデルに追加(最初はnullable=Trueにしておく)。
  2. autogenerateでマイグレーションスクリプトを生成(add_columnが生成される)。
  3. 生成されたスクリプトを編集し、nameカラムのデータをfirst_namelast_nameに分割してINSERTする処理を追加する。
  4. nameカラムを削除する新しいマイグレーションを作成する。

ステップ3のスクリプト編集がポイントです。upgrade()関数内で、スキーマ変更(op.add_column)の後に、データ移行処理を記述します。データ操作には、SQLAlchemy Core Expression Languageを使うのが便利です。

“`python

データマイグレーションを含むマイグレーションスクリプトの例

from alembic import op
import sqlalchemy as sa

… (revision IDなど)

usersテーブルの定義をインラインで取得

users_table = sa.table(‘users’,
sa.column(‘name’, sa.String),
sa.column(‘first_name’, sa.String),
sa.column(‘last_name’, sa.String)
)

def upgrade() -> None:
# 1. カラムを追加
op.add_column(‘users’, sa.Column(‘first_name’, sa.String(50), nullable=True))
op.add_column(‘users’, sa.Column(‘last_name’, sa.String(50), nullable=True))

# 2. データを移行 (nameをスペースで分割)
op.execute(
    users_table.update().values(
        first_name=sa.func.split_part(users_table.c.name, ' ', 1),
        last_name=sa.func.split_part(users_table.c.name, ' ', 2)
    )
)

# 3. カラムをNOT NULLに変更
op.alter_column('users', 'first_name', nullable=False)
op.alter_column('users', 'last_name', nullable=False)

def downgrade() -> None:
# downgradeでは逆の操作を行う
# (ここでは簡略化のためカラム削除のみ)
op.drop_column(‘users’, ‘last_name’)
op.drop_column(‘users’, ‘first_name’)
``
この例では、
op.execute()を使ってSQLAlchemyのUPDATE文を実行し、データを移行しています。downgrade()`でもデータの復元処理を記述することが重要ですが、複雑になるケースも多いため、どこまで対応するかはプロジェクトの方針によります。

第5章: チーム開発と高度なトピック

一人での開発からチームでの開発に移行すると、Alembicのさらに高度な機能が必要になります。

5-1. ブランチとマージ

複数の開発者が、同じmaster(またはmain)ブランチからそれぞれフィーチャーブランチを切り、別々の機能開発でスキーマ変更を行った場合を想像してください。

  • 開発者A: feature/add-postsブランチでpostsテーブルを追加。
  • 開発者B: feature/add-tagsブランチでtagsテーブルを追加。

開発者Aが先にリビジョンaaaaを作成し、開発者Bもほぼ同時にリビジョンbbbbを作成します。この時点では、どちらのdown_revisionも同じ親リビジョンを指しています。これが「ブランチ」が発生した状態です。

alembic headsコマンドで確認すると、複数のhead(末端リビジョン)が存在することがわかります。

bash
$ alembic heads
aaaa (head)
bbbb (head)

このままでは、マイグレーションの履歴が分岐してしまっています。これらのブランチを統合するために、「マージリビジョン」を作成します。

bash
alembic merge aaaa bbbb -m "Merge posts and tags features"

このコマンドは、aaaabbbbの両方を親に持つ新しいリビジョンccccを生成します。ccccupgrade()downgrade()関数は空です。このリビジョンの役割は、分岐した履歴を一つにまとめることだけです。

マージリビジョン作成後の履歴は以下のようになり、headが一つにまとまります。

(base) -> ... -> (親リビジョン) --+--> aaaa --+--> cccc (head)
| |
+--> bbbb --+

これにより、Gitでブランチをマージするのと同じように、Alembicでもスキーマ変更の履歴を安全に統合できます。

5-2. 複数のデータベースを扱う (--nameオプション)

一つのアプリケーションで、メインのDBと分析用のDBなど、複数のデータベースを管理したい場合があります。Alembicはこのようなケースにも対応できます。

  1. alembic.iniの設定:
    [alembic]セクションをコピーして、データベースごとにセクションを作成します。

    “`ini
    [alembic]
    script_location = project/migrations

    [db1]
    script_location = alembic_db1
    sqlalchemy.url = …

    [db2]
    script_location = alembic_db2
    sqlalchemy.url = …
    “`

  2. マイグレーション環境の初期化:
    initコマンドに--nameオプションを付けて、データベースごとに環境を作成します。

    bash
    alembic init --name db1 alembic_db1
    alembic init --name db2 alembic_db2

  3. コマンドの実行:
    以降のAlembicコマンド(revision, upgradeなど)を実行する際に、-nまたは--nameオプションで操作対象のデータベースを指定します。

    “`bash

    db1用のリビジョンを作成

    alembic -n db1 revision –autogenerate -m “Initial migration for db1”

    db2を最新にアップグレード

    alembic -n db2 upgrade head
    “`

5-3. 本番環境での運用

本番環境でAlembicを運用する際には、いくつかのベストプラクティスがあります。

  • CI/CDへの組み込み:
    デプロイパイプラインの中にalembic upgrade headコマンドを組み込み、アプリケーションコードのデプロイ前に必ずマイグレーションが実行されるようにします。これにより、マイグレーションの適用漏れを防ぎます。

  • --sqlオプションによるレビュー:
    変更を適用する前に、どのようなSQLが実行されるかを確認したい場合があります。--sqlオプションを付けると、Alembicはデータベースに接続せず、実行されるであろうSQL文を標準出力に表示します。

    bash
    alembic upgrade head --sql

    これにより、DBA(データベース管理者)や他の開発者がSQLをレビューすることが可能になり、安全性が向上します。

  • ダウンタイムへの配慮:
    ALTER TABLEなど一部の操作は、テーブルにロックをかけ、長時間サービスが停止する(ダウンタイムが発生する)可能性があります。大規模なテーブルに対する変更は、ユーザーアクセスの少ない時間帯に行う、またはオンラインスキーマ変更ツール(pg_repackなど)と組み合わせるなどの戦略が必要です。

  • バックアップの徹底:
    言うまでもありませんが、マイグレーションを適用する前には、必ずデータベースのバックアップを取得してください。万が一、downgradeでも復旧できない問題が発生した場合の最後の命綱となります。

5-4. よくある問題とトラブルシューティング

  • autogenerateが変更を検出しない:

    • env.pytarget_metadataが正しく設定されていますか?
    • モデルを定義しているファイルが正しくインポートされていますか?
    • CHECK制約やserver_defaultの変更など、autogenerateがサポートしていない種類の変更ではありませんか?
  • Target database is not up to date. エラー:
    これは、データベースのalembic_versionテーブルが指すリビジョンと、versionsディレクトリの履歴が食い違っている場合に発生します。手動でDBスキーマを変更してしまった、Gitで古いブランチに戻った、などの原因が考えられます。
    本当に現在のDBスキーマが特定のリビジョンと同じ状態であると確信できる場合は、alembic stamp <revision_id>コマンドでalembic_versionテーブルを強制的に更新できます。しかし、これは慎重に使うべきコマンドです。

まとめ

この記事では、SQLAlchemyのエコシステムにおける強力なデータベースマイグレーションツール、Alembicについて、その基本から応用までを網羅的に解説しました。

私たちは、以下の重要なステップを学びました。

  • Alembicがなぜ現代のアプリケーション開発に不可欠であるかと、それがもたらすバージョン管理、再現性、安全性といったメリット。
  • alembic initによるプロジェクトのセットアップと、alembic.iniおよびenv.py重要な初期設定
  • alembic revision --autogenerateによるマイグレーションスクリプトの自動生成alembic upgradeによる適用alembic downgradeによるロールバックという基本的なワークフロー。
  • カラムの追加・変更、データマイグレーションといった、実践的なスキーマ変更への対応方法。
  • ブランチとマージ、複数DBの管理、本番運用など、チーム開発で役立つ高度なトピック

Alembicを導入し、スキーマ変更をコードとして規律正しく管理する文化をチームに根付かせることで、開発プロセスは劇的に改善されます。手作業によるミスや環境間の差異に悩まされることがなくなり、開発者は自信を持って、より速く、より安全にアプリケーションを進化させることができるようになります。

もちろん、Alembicにはここで紹介しきれなかった機能もまだたくさんあります。ぜひ公式ドキュメントも参照し、その可能性をさらに探求してみてください。

データベーススキーマ管理は、もはや恐れるに足りません。Alembicをあなたのツールボックスに加え、堅牢で保守性の高いアプリケーション開発の世界へと踏み出しましょう。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール