ActiveSupport#present? の危険性
何が起こったか
Railsのバージョンアップの作業をしていて、画面の動作確認を行っていたところ、一部のページで100秒を超えるようなレイテンシが発生していました。

DatadogのLATENCYではなかなか見ないm(分)という単位
調べてみると、ActiveSupport で定義されている present? メソッドを使って、画像の有無の確認をしている(無い場合はデフォルト画像を出すような)部分で、問題が発生していました。
原因
プロダクトで使っているRailsをアップデートする作業の中で carrierwave という gem のバージョンを上げていました。
CarrierWave は、Ruby on Rails でファイルアップロードを簡単に扱うための gem です。
そのアップデート内容に 3.1.0 で Add CarrierWave::Storage::Fog::File#empty? の実装が含まれていました。
以下が empty? が入ったCommitです。
なぜ present? で問題が発生するのか
以下のように ActiveSupport の present? メソッドは blank? メソッドの否定を返します。
そして blank? メソッドは empty? メソッドがあればそれを使う実装になっています。
今までは empty? メソッドが無かったため blank? メソッドでファイルの存在確認だけして false が返り、その否定なので present? メソッドは true になっていました。
しかし、今回の変更により empty? メソッドが新たに実装されたことで present? メソッドを呼ぶとその処理が実行されるようになったのです。
結果、empty?メソッドの実装がアップロードされているファイルのサイズが0かどうかまで確認するものだったために、クラウド上のストレージまでHEADでアクセスすることになりました。
そして数十枚の画像を表示する画面で、一枚一枚の画像でクラウド上へHEADのアクセスが走ることになり、大きなレイテンシが発生してしまった、というわけです。
現在 gem での対応が、以下のIssueで検討中のようです。
問題への対応
present? メソッドを使わないようにしました。
具体的には url メソッドはファイルが無い場合には nil を返すので、その結果を nil? を使って判定するようにしました。
- image_object.present?
+ image_object.url.nil?
まとめ
Railsアプリのバージョンアップ作業で、何かのObjectに empty? が新しく実装されたら、ちょっと気を付けましょう!
present? や blank? は便利で色んなところで使いがちですが、こういう問題が発生した際は nil? などで回避できるかも知れませんので、試してみてください。
Discussion