Edited at

Rails アプリを eager_load! してみることでプロダクション環境での出落ちを未然に防ぐ


環境

Rails 5.2.1.1


デプロイしたら Rails が出落ちした

ある日いつものように Rails アプリをデプロイすると、未定義の module を include しようとしていたことが原因で Rails がアプリケーションの読み込みに失敗し、uninitialized constant ... (NameError) を吐いてクラッシュしてしまいました。


開発環境では動いたんだけど…

本番またはステージング環境では、開発環境と違って環境毎の設定ファイルに


config/environments/production.rb

config.eager_load = true


と書くことで Rails が起動時にアプリ全体を読み込むように設定すると思いますが、このときアプリ全体に少しでも読み込みに失敗するようなコードがあると Rails はクラッシュしてしまいます。

今回はデプロイ前に RuboCop の警告がないことを確認し、RSpec による単体テストを通してはいましたが、該当部分を通るテストケースがなかったために、読み込めないソースコードが存在することに気づくことができませんでした。


出落ちを未然に防いだ

単体テストが Rails アプリの全てのコードが読み込まれる程度にはしっかり書かれているのが理想ですが、完璧に網羅し続ける自信はありません。そこで、CI の Job に以下のコマンドを追加し、CI パイプライン内で Rails がアプリケーションの読み込みでエラーを起こさないことを確認するようにしました。

bundle exec rails runner Rails.application.eager_load!

これでデプロイ前に、少なくとも Rails が読み込みに失敗するようなコードが無いことを CI で保証できるようになりました。別のアプローチとして、以下のようにテスト時に環境変数で eager_load を行うかを流し込めるようにするアプローチも見つけました1 が、テストケースの失敗とアプリケーションの読み込みの失敗を分けて把握したかったので、一旦上のような形に落ち着きました。


config/environments/test.rb

config.eager_load = ENV.fetch('EAGER_LOAD', '0') == '1'