はじめに
laravelで困ったことがありました(いつもですが)。
恐らくですがlaravelのviewキャッシュが原因の、bladeファイルで作成した画面が真っ白になるという不具合に遭遇しました。ざっくりしているのですが意図的に再現することも難しく。不具合当初に取得したスナップショットを立ち上げれば再現が可能なんですけども。
どう起こったか
AMIからEC2インスタンスを立ち上げ、そのインスタンスに対してブラウザからページアクセスした際に遭遇しました。AMIはLaravel環境として動いていた既存のインスタンスから取得したものなので、復元した時点でキャッシュデータも内部に保持しています。
# 立ち上げたインスタンスの内部には、すでに既存の各キャッシュデータが存在する
ls /laravel_project/app/storage/framework/
cache sessions testing views
ベースのインスタンス稼働環境とほぼ同等の環境で立ち上げたのですが、一部ページの画面が真っ白になり動作しないという現象に遭遇しました。
AWSのオートスケーリング機能を使用してリソースを調整しているシステムの場合は、ベースとなるAMIを取得して起動テンプレートに設定→AMIに準拠したインスタンスがスケールアウトするという流れになるため、この運用を行うケースがあるかと思います。
なぜviewのキャッシュが原因だと思ったか
白く表示されたページではHTTPステータスは200が返っており、またlaravel, php-fpm, nginx等にはエラーログが特に出力されていませんでした。
あれこれ調査した結果、対象ページのbladeファイルをvimなどで開く→適当に改行して保存すると正常に表示されました。またviewキャッシュを全削除(php artisan view:clear
)した場合も直りました。こちらから古いキャッシュデータが何かしら噛み合うことで白い画面の問題が発生していると考えました。
対策する
今回の現象はviewキャッシュが残っている場合に発生すると上記で仮定しましたので、AMIの取得前にviewキャッシュをクリアしておくことで対処ができそうです。ただしviewキャッシュはページへのアクセスがあった場合に自動で生成されるため、キャッシュクリアしたとしてもAMIの取得前にアクセスがあるとviewキャッシュが再生成されてしまいます。というよりそもそもAMI取得前にいちいちキャッシュを消すのも面倒なのでなんとかできないか考えます(あと可能な限りキャッシュは消さずに残したい)。
chatGPTに聞いてみる
ググりつつ、並行してchatGPTくんにも聞いてみることにしてみます。
なるほど。expire
でキャッシュの期限を設定できるのであれば確かに有効かもしれません。例えばexpire
を1日程度に設定しておけば、1週間前に取得したAMIからスケールアウトが発生した場合、スケールアウトの時点ではキャッシュの有効期限が切れているため、該当のキャッシュデータは破棄されつつ本来のviewファイルへとアクセスされる挙動が予想できるので今回の原因について予防できそうです。
ついでに、view.php
についてあまり知らなかったので追加で聞いてみました。
ふむふむ。ドキュメントも貰っておきましょう。
資料が整ったので実際に動作確認してみます。有効期限は一旦60分で。
# app/config/view.php
<?php
return [
'expire' => 3600, // 追加
...
書き込んだのちphp artisan config:cache
で設定を適用し、php artisan view:clear
でviewキャッシュを削除しました。これでstorage/framework/views
へと溜まるキャッシュデータに期限が付与され、60分(3600秒)後には消えるはずです。
…
…
…
消えず。定義した値が読まれているかをtinkerで確認しましたが認識されています。
php artisan tinker
> config('view.expire')
= "3600"
次にview.php
が実際に動作しているかを確認しました。viewのキャッシュデータの配置先を設定できるcompiled
という設定値があるのですが、こちらを適当に編集したのち再びconfig:cache
で読ませました。するとデータ配置先についてのエラー( Please provide a valid cache path.
)が出たので動作してはいるようです。
そこでchatGPTくんが指したexpire
という設定値がそもそも存在するのか確認しました。とりあえず先ほど出力してもらったドキュメントを確認します。
…
…
…
全然見当たらない。というかview.php
についての情報が少ない。ちなみにgithubのlaravel10.xブランチのview.php
は以下です。
なんなら、最新のlaravel11.x及びmasterブランチではview.php
自体が消えています。
使われなかったんでしょうか… 10.x版のドキュメントを軽く眺めたのですがview.php
に定義できる値の一覧などは見つからず。というか#view-configurationなんて箇所無いやないかい。configに定義できる値の一覧ってコード内から頑張れば追いかけられそうなのですがどうなんでしょうか。
動作しなかったけどexpire
って定義は存在しそうなんですよね。別の設定ファイルで上書きされてたパターンでしょうか。分かったら追記します。
色々あってviewキャッシュのデータをconfig下で管理することは諦めました。今回やりたいことは「インスタンスを立ち上げた時にviewキャッシュを削除する」という内容なので別のツールで対応できないか改めて調べます。
cronで実行する
定期的にphp artisan view:cache
を実行させてみてはどうか考えました。ただ以下の理由からもう少し良い手法がないかを探すことにしました。
- 通常通り稼働しているインスタンスにも設定するので(こちらからベースのAMIを取得するため)、そちらでもキャッシュデータを定時で消してしまう
- 結局スケールアウトしたインスタンスはcronが実行されるまでviewキャッシュが残ったまま
仮にcronで毎日0時に実行するならこんな感じかと。
0 0 * * * php /laravel_project/artisan view:clear >> /dev/null 2>&1
constructで制御する
public function __construct() { exec('php /full/path/to/artisan view:clear'); }
問題が起こりそうなページを管理するコントローラクラスのコンストラクタに定義することで確かに実現できます。今回は採用しませんでしたが、こういった発想は持っておきたいですね。
インスタンス起動時に設定できるUser Dataで対応する
chatGPTくんに泣きついたところ、こちらも教えて貰えました。
上記だと1と3の手法について関心を持ち、特に自分の中の要件と適しているのが1かなと思ったので調べました。
GCPでは起動スクリプト(Startup Scripts), Azureではカスタムスクリプト拡張(Custom Script Extension)と呼ぶようです。
AWSのUser Dataについては下記。
設定することでインスタンスの起動時に決めた通りのコマンドを実行することができます。
場所はEC2 > インスタンス > インスタンスを起動
から、設定の一番下あたりの高度な設計 > ユーザーデータ
で設定が可能です。起動テンプレートを使っているのであれば起動テンプレート > テンプレートを変更(新しいバージョンを作成) > 高度な設計 > ユーザーデータ
で同じ箇所に辿り着けます。
#!/bin/bash
# shell operation check
# echo "User Data executed at $(date)" >> /var/log/user-data.log
php /laravel_project/artisan view:clear >> /dev/null 2>&1
こちらで期待していた挙動である「インスタンスを立ち上げた時にviewキャッシュを削除する」ことを実現できました。User Dataの動作確認をしたい場合はecho
の部分のコメントを外してから起動させることで、インスタンス内部の/var/log/user-data.log
へと出力する内容が書き込まれているかどうかで確認できます。
おわりに
特殊な対応が必要だったにも関わらず対応できました。AWSなどのクラウドサービスの対応可能な範囲にも感謝ですが、最近はchatGPTの凄さに驚くばかりです。
要件を噛み砕いてシンプルな質問として作文できる力と、出力された内容を保証できる資料を自分で特定する力の2つがあれば非常に力強い味方になってくれます。プログラマの仕事が無くなるかもというのもあながち間違いではないかもしれません。そうならないように技術知識だけでなく、普遍的に必要であるコミュニケーション等のスキルも磨いていきたいところです。
おまけ:laravel11とview.phpに関する記事など
自分みたいに探す人がいて、この記事に辿り着く方がいるかもしれないので参考に。
ちなみに今回の現象はlaravel10で遭遇しました。