この記事は「READYFOR Advent Calendar 2022」の3日目の記事です。
前日2日目は「Google アナリティクス 4(GA4)パラメータをJSON配列形式にして飛ばす方法」でした。
ようするに
Railsアプリケーションでは以下の設定がおすすめ。
# config/environments/test.rb
config.eager_load = ENV["CI"].present?
Rails.config.eager_load
とは
アプリケーションは、コードが大きくなればなるほど読み込みに時間がかかってしまいます。
例えば「ちょっと動作確認のためにconsoleを開きたいんだけどな」と考えコンソールを開いたとき、毎回この読み込みが発生すれば開発効率が低下するのは火を見るより明らかです。
そこでRailsではコードの読み込みを最小限にするための遅延読み込みをサポートしています。Ruby定数名からファイルを探し出して読み込む仕組みで、開発用途なら非常に便利な機能ですね。
一方production用途ではリクエストを高速に返したいので、遅延読み込みではなく、Rails起動時にまとめて読み込んでいます。この切り替えをRails.config.eager_load
によって行っています。
ここまではなんとなく知っていました。
productionにdeployできない事件
あるとき私は新機能を実装していました。手元の開発環境で動作確認し、CIのテストもパスしています。ところがdeployしようとしたとき、何のメッセージもなく突然Railsプロセスが終了してしまう問題に遭遇しました。
社内のRailsに詳しい方に調査して頂いたところ、私の書いたスクリプトが原因だと指摘されました。
そのスクリプトは開発用途に実装した小さなCLIツールで、引数から小さな機能を提供するものでした。このCLIツールは引数が足りなければexit 1
を実行するようになっていました。
if ARGV.empty?
exit 1
end
「開発時でもCIでも問題なかったのになぜ?」
私は困惑しました。
eager_loadは「とにかく全部require」
運用しているアプリケーションではRails env毎のRails.config.eager_load
設定は以下のようになっていました。
- development:
false
- test:
false
- production:
true
また、当時運用していたRails v6.0ではまだZeitwerkに移行していなかったので以下のRails内コードが走っていたことになります。
# railties/lib/rails/engine.rb
def eager_load!
# Already done by Zeitwerk::Loader.eager_load_all. We need this guard to
# easily provide a compatible API for both zeitwerk and classic modes.
return if Rails.autoloaders.zeitwerk_enabled?
config.eager_load_paths.each do |load_path|
# Starts after load_path plus a slash, ends before ".rb".
relname_range = (load_path.to_s.length + 1)...-3
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
require_dependency file[relname_range]
end
end
end
ようするに指定dir以下は全部require
するシンプルなコードのようです。Zeitwerkでもできるだけ互換性を保つようなので似たような仕組みだと思われます(未検証)
このコードによって先程の小さなCLIツールもrequire
され、exit 1
が発動し、何のメッセージもなく終了していたということでした……。Rails 10年やってて知らない私が無知でした……。
この小さなCLIツールは役目を終えていたので、このファイルを消してdeployはできるようになりました。
再発防止策
この問題はproductionでのみ再現します。なんとかもう少し早く気づくことができないのでしょうか?
rails guidesではこの問題について次の解決策を提示しています。
# config/environments/test.rb
config.eager_load = ENV["CI"].present?
この方法なら手元でのテスト実行はでは開発効率のためeager_loadをoffにしつつ、CI上ではeager_loadするので今回の問題に気づくことができそうです。
上記コードを導入し、はれて問題解決となりました。
4日目
次の4日目は「DevHRがカンバンを導入した話」です。