Turso を本番DBにできず SQLite + Fly Volume に切り替えた
SaaS で「1社1DB」の物理分離をやろうとして Turso (libsql) を入れたら、SQLAlchemy 2.0 async + alembic で本番に通る経路が見つからず、SQLite + Fly Volume + Litestream に切り替えた記録。
「1社につき Fly.io app 1 つ + DB 1 つ」をテナントごとに丸ごと分ける構成にしようとしていた。
DB は最初 Turso(libsql ベースのマネージド SQLite)にする予定だった。Fly.io app と Turso DB を作って、fly secrets set DATABASE_URL=libsql://... TURSO_AUTH_TOKEN=... まで通った。
fly deploy のビルドも通った。詰まったのは release_command に書いた alembic upgrade head が起動するところ:
sqlalchemy.exc.NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:libsql調べた範囲
PyPI と GitHub を見て回って、こうなっていた(2026-06 時点):
sqlalchemy-libsql0.2.0 は sync ダイアレクトのみ。アプリは FastAPI + SQLAlchemy 2.0 の async で書いてあるので、ここに sync を混ぜる構造になるlibsql-experimentalは async 対応だが、SQLAlchemy に繋ぐパッケージは公式には無い- Turso の embedded replica(ローカルに SQLite ファイルを置いて、裏で Turso と同期する方式)は async から使えるが、アプリ側にレプリケーションの同期処理を組み込む必要がある
どれも、SQLAlchemy 2.0 async + alembic 構成の本番DBドライバとして「DATABASE_URL を差し替えるだけで動く」状態には今は無い。
切り替え
きちんと統合すれば数日かかるし、Tursoであることのメリットはそこまで大きくないかなと思い、下記に構成を変えた。
[Fly.io VM (1社につき1台)] app (FastAPI) └─ /data/logicky.db (SQLite。Fly Volume にマウント) └─ Litestream → Cloudflare R2 (継続バックアップ)fly.toml 側はこれだけ:
[env] # 4スラッシュ = 絶対パス DATABASE_URL = "sqlite+aiosqlite:////data/logicky.db"
[[mounts]] source = "logicky_data" destination = "/data" initial_size = "1gb"alembic upgrade head は aiosqlite ドライバでそのまま通る。
物理分離の話は変わらない。VM が1社ごとに別、Volume も1社ごとに別、DB ファイルも別。テナント間でデータが混ざる経路は依然として無い。
切り替えで失ったもの
- マルチリージョンの読み取りレプリカ:今は国内のお客様しか想定していないので、効きが薄い
- Turso の DB を API で作れる仕組み:テナント数が一桁のうちは
fly apps create+fly volumes createを手で叩ける範囲
いつ Turso に戻すか
例えば、数百DBが必要になったり、マルチリージョンが重要になったりする場合に、Tursoへの切り替えをまた検討すればいいかなと思った。 SQLite ファイルなので、移行は中身をエクスポートして Turso に流し込むだけで済む形にはなる。