みなさん、お疲れ様です
AIForwardの末永です。
今日はSymfonyアプリケーションをデプロイする時に遭遇した、ちょっと厄介なエラーについて共有したいと思います。Railway環境にデプロイしようとしたら、キャッシュウォームアップでデータベース接続エラーが出ちゃって...結構ハマったんですよ。でも、インメモリSQLiteを使った解決策を見つけたので、同じ問題で困ってる人の助けになればと思って記事にしました
はじめに
最近管理するサーバーが増えてサーバー費とかパスワードとかの管理がめんどくさくなってきたので、諸々railwayに移行してたんですが、今回Symfonyアプリケーションをコンテナ環境(Railway、Docker等)にデプロイする際に、cache:warmupコマンドでデータベース接続エラーが発生しちゃいました。
特にDoctrine ORMを使ってる場合、この問題によく遭遇するんですよね。
環境情報
まず、僕が使ってた環境はこんな感じです:
- Symfony: 6.4.*
- PHP: 8.3
- Doctrine ORM: 3.5.2
- デプロイ環境: Railway (Nixpacks v1.38.0)
- 本番DB: MySQL
問題の発生
エラー内容
デプロイ時にこんなエラーが出ました:
RUN cd backend && APP_ENV=prod APP_DEBUG=0 php bin/console cache:warmup --env=prod
.internal failed: Name or service not known
ERROR: failed to build: failed to solve: process did not complete successfully: exit code: 255
設定(問題が発生した状態)
# nixpacks.toml
[phases.build]
cmds = [
"cd backend && touch .env",
"cd backend && APP_ENV=prod APP_DEBUG=0 php bin/console cache:warmup --env=prod"
]
この設定でビルド時にキャッシュウォームアップを実行しようとしたら、データベース接続エラーが発生しちゃったんです。
問題の原因分析
Symfonyのキャッシュウォームアップって何してるの?
そもそもcache:warmupコマンドって何してるのか。このコマンドは本番環境でのパフォーマンス向上のために、以下の処理をしてくれるんですね:
- サービスコンテナのコンパイル
- ルーティングキャッシュの生成
- テンプレートキャッシュの作成
- Doctrineメタデータキャッシュの生成 ← ここでDB接続が必要!
- プロキシクラスの作成
4番目でDB接続が必要になるわけです。
なぜDoctrine ORMでDB接続が必要になるのか
Doctrineって、エンティティクラスの構造を検証したり、データベーススキーマとの整合性をチェックしたりするんですよ。例えばこんな感じのエンティティがあったとして:
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: 'string', length: 255)]
private string $email;
// Doctrineはこれらの定義を検証するためにDB接続を試行
}
Doctrineはこれらの定義が正しいかチェックするために、実際にデータベースに接続しようとするんです。
デプロイ環境での問題
ビルド時って:
- まだデータベースサービスが利用不可
- 環境変数の
DATABASE_URLは設定されてるけど、実際の接続先が存在しない -
mysql.railway.internal:3306への接続試行 → 失敗!
つまり、「データベースがまだ立ち上がってないのに接続しようとしてエラーになる」っていう状況です。
解決策:インメモリSQLiteの活用
ここで、ビルド時のキャッシュ生成では実際のデータは不要で、Doctrineがエンティティ構造を理解できれば十分なので、インメモリSQLiteを一時的なデータベースとして使うことにしました
実装
解決策はシンプルで
# nixpacks.toml
[phases.build]
cmds = [
"cd backend && touch .env",
"cd backend && DATABASE_URL=sqlite:///:memory: APP_ENV=prod APP_DEBUG=0 php bin/console cache:warmup --env=prod"
]
[start]
cmd = "cd backend && echo 'Starting PHP server on port '$PORT && php -S 0.0.0.0:$PORT -t public"
DATABASE_URL=sqlite:///:memory:を追加するだけで、インメモリSQLiteが使われます。
インメモリSQLiteの役割
ビルド時はインメモリSQLiteを使ってキャッシュを生成し、実行時は本物のMySQLを使うという仕組みになってます
他のアプローチとの比較
アプローチ1: ビルド時キャッシュ生成をスキップ
最初はこれも考えたのですが、問題点が:
- 起動時間が長くなる(初回リクエスト時にキャッシュ生成)
- スケーリング時に各コンテナで個別にキャッシュ生成が必要
これじゃあパフォーマンス的に良くないですよね。
アプローチ2: Doctrine機能を無効化
これも考えましたが:
- Doctrineの機能が制限される
- 本番環境でのパフォーマンスが低下
本末転倒です(笑)
アプローチ3: インメモリSQLite(推奨)
やっぱりこれが一番!
- ビルド時にフルキャッシュ生成
- 外部依存なし
- 本番環境の機能はそのまま
- 高速起動
トラブルシューティング
よくあるエラーと対処法
僕も遭遇したエラーをいくつか紹介しますね。
エラー1: SQLite拡張が無効
ERROR: PHP extension sqlite3 is required
これは簡単に解決できます:
RUN apt-get install -y sqlite3 php-sqlite3
エラー2: メモリ不足
キャッシュ生成でメモリが足りなくなることもあります。その場合は:
[phases.build]
cmds = [
"cd backend && php -d memory_limit=512M bin/console cache:warmup --env=prod"
]
他のフレームワークとの比較
ちなみに、LaravelやDjangoではこの問題は起きないんですよ。
Laravelの場合:
php artisan config:cache
php artisan route:cache
DB接続不要です。
Symfonyだけこの問題が発生する理由は、Doctrine ORMとキャッシュシステムが密結合してるからなんですね。でも、その分高機能なので、トレードオフですね。
まとめ
SymfonyアプリケーションのDeployment時のDB接続エラー、インメモリSQLiteを使えば簡単に解決できます!
重要なのは、この一行:
DATABASE_URL=sqlite:///:memory: APP_ENV=prod php bin/console cache:warmup --env=prod
これでSymfonyの高機能とコンテナ環境の制約を両立できちゃいます。
同じ問題で困ってる方の参考になれば嬉しいです!質問があればコメント欄でお気軽にどうぞ。
それではまた次の記事でお会いしましょう!