因果応報タイム(Feat. Alembic)
2025年05月11日
最近まで「時間がない」という言い訳を盾に、ずっと後回しにしていたブログのリファクタリング作業を、ここ数日でようやく進めているところだ。
まずは投稿リストを /posts に移し、既存の / には新しいランディングページを当てた。
エンドポイントも post/{post_id} から posts/{post_id} に変更して、RESTfulに整理。
HTMLテンプレートのデザインも少しずつ手を加えていった。
この過程で特に実感が大きかったのが、BuildKitのキャッシュ最適化とDiscordのWebhook通知導入だった。

デプロイ速度は3〜4分から40秒にまで短縮され、Actions画面を覗かなくても通知でログが飛んでくるから、作業への集中も保てた。

…とはいえ、もともと今日やろうとしてたのはCRUDのリファクタリングだった。
だけど1000行近い投稿コードを目の前にした瞬間、自然と視線が下に向かった。
とりあえずモデルだけでも整理してみるか…
この安易な選択が、地獄の扉を開けることになるとは思いもしなかった。
モデルを整理してみると、いつか使うつもりで追加したけど一度も使われていないカラム、
そもそも何の意図で作ったのか記憶すらないカラムがあった。
逆に、今運用していて不便を感じるところを補うには、新たにテーブルやカラムを追加する必要もあった。
けど問題があった。
最後にリビジョンを切ったのは6ヶ月前で、それ以降一度もマイグレーションを行っていなかった。
Alembicを使ってたな、くらいの記憶を頼りに、手探りでコマンドや使い方を調べ始めた。

Alembicとは?
Alembicは、SQLAlchemyベースのプロジェクトにおいてDBマイグレーションを自動で管理してくれるツールだ。
簡単に言えば、モデルに変更を加えた時、その変更をDBにも反映してくれるバージョン管理システム。
たとえばusersテーブルに新しいカラムを1つ追加したとする。
その都度 ALTER TABLE を書いて適用するのは面倒だし、ミスも多い。
Alembicはその変更を「バージョンファイル」として migrations/versions/*.py に記録し、
alembic upgrade head といったコマンドでDBの状態を最新版に保ってくれる。
もっとわかりやすく言うなら、DBをGitみたいにバージョン管理してくれる奴だ。
で、その元祖であるGitと同じく、一度こじれると地獄を見る。
地獄のはじまり
「もう直接DBに接続してSQL手打ちするか…?」とも思ったけど、
消すカラムも多いし、新しく作るテーブルもあるし、
APIインスタンスとDBインスタンスは分かれていて、models.py にも反映させなきゃならんという問題があった。
でも正直、Alembicを使うにはうちの環境がめんどくさすぎた。
AlembicはAPI側のDockerコンテナ内で動いていて、DBはまた別のコンテナ。
まずはその2つがちゃんと繋がってるのか確認しようと、
docker exec -it main bash
echo $DATABASE_URL
と叩いてみたところ、返ってきたのは mysql+pymysql://:@:/。
.env から値は読んでるっぽいけど、中身はこうなっていた。
DATABASE_URL = mysql+pymysql://${DB_USERNAME}:${DB_PASSWORD}@${DB_ENDPOINT}:${DB_PORT}/${DB_NAME}
DB_USERNAME = john
DB_PASSWORD = doe
DB_ENDPOINT = 0.0.0.0
DB_PORT = 0000
DB_NAME = DB
.env ファイルでは変数のネストがサポートされてないから、これは当然展開されない。
つまり、接続はされてるけどURLが成り立ってない状態だった。
「そもそもこんな書き方いらんやん」って気づいて、.env をシンプルに書き換えた。
環境を整えて、ホスト側からマイグレーションを試みた。
docker exec -it main bash
cd /example/app
alembic revision --autogenerate -m "Description"
alembic upgrade head
INFO [alembic.autogenerate.compare] Detected added column 'posts.series'
INFO [alembic.migration] マイグレーション完了
Generating /example/app/migrations/versions/00000000_add_series_column.py
... done
Workbenchでもちゃんとカラムが追加されているのを確認できた。
…で、ローカルのmodels.pyを保存してコミットしたら、502 Bad Gateway。
ログを見ると、Alembicがリビジョンファイルを見つけられなくてAPIコンテナがクラッシュしていた。
main | FAILED: Can't locate revision identified by '000000000'
main | ERROR [alembic.util.messaging] Can't locate revision identified by '000000000'
main exited with code 255
これ、理由は明確だった。
- マイグレーションはコンテナ内でやった → リビジョンも当然コンテナ内にある
- けど、そのリビジョンはGitにもDockerイメージにも含まれてない
- DB上には alembic_version = '000000000' と記録されている
- でも肝心のファイルが存在しない
- だからマイグレーションが破綻して、docker-compose の起動時に alembic upgrade が走ってAPIが死ぬ
一旦復旧だけはしようと、ダミーのファイルを作ってコミット。
"""recreate series column"""
revision = '000000000'
down_revision = '111111111'
def upgrade():
pass
def downgrade():
pass
でもこれじゃ根本的な解決にならんので、
ローカルでリビジョン生成 → ホストで upgrade head だけする方針に切り替えた。
そしたら今度は alembic: command not found
poetry show ではちゃんとインストールされてるのに、/.venv/bin/python が壊れてるっぽい。
bad interpreter: no such file or directory
もう頭痛がしてきたが、ぐっと堪えて
rm -rf .venv
poetry install --no-root
で仮想環境を全消しして再インストール。
やっとAlembicが動いたと思ったら、
line 7, in <module>
from app.database import Base, SQLALCHEMY_DATABASE_URL
ModuleNotFoundError: No module named 'app'
プロジェクト構成がこうだったから、
example/
├── app/
│ ├── alembic.ini
│ ├── migrations/
│ ├── models.py
│ └── ...
├── pyproject.toml
└── ...
パスの解決ができてなかった。
調べたら alembic.ini はルートに置くのが普通らしく、素直に移動した。
今度はDBに繋がらない。
sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server on '0.0.0.0' (timed out)")
見ればわかる。Private IPだった。
セキュリティ的にPublicを開けたくなかったので、ポートフォワードを使うことにした。
ssh -i ~/.ssh/key.pem -L 3306:0.0.0.0:3306 ubuntu@0.0.0.0
で、.env のDB URLを localhost:3306 にしたかったけど、そうするとAPI側が自分自身を見に行ってしまうので、.env を2つに分けた。
あと、alembic.ini と migrations/ をルートに移動したので docker-compose.yml の修正も必要だった。
全部終えたあと、ローカルで改めてリビジョンを生成。正常動作確認。
そのままコミットして alembic upgrade head を走らせるとマイグレーションも問題なし。
ようやく地獄から抜け出せた。
結論
ひとつひとつは大したことない選択だった。
でも積み重なって絡み合って、
コンテナ、Git、環境変数、Dockerfile、Poetry、Alembic、DB接続、マイグレーション方針
全部を一斉に手術する羽目になった。
いつだってそうだ。
あのときは「まぁいいや」で済ませた判断が、結局、未来の自分の背中に刃を突き立てる。
でも、それが必ずしも悪いことばかりじゃない。
この複雑な構成だったからこそ、実務さながらのトラブルシューティングを体験できたし、
それを整理して乗り越える中で、自分がちゃんとレベルアップしている感覚があった。
そして、はっきり確信した。
開発をやればやるほど、結局「設計がすべて」だ。
最初に10分ケチっただけで、あとから10時間かかるなんてことは珍しくもない。
最後に、これから技術ブログを始めようとしてる人にアドバイスをひとつだけ。
小規模なプロジェクトなら、Alembicなんて使うな。
素直にSQL手書きしとけ。
そっちの方がメンタルにいい。
カカオ
グーグル
ネイバー