🚂

ActiveSupport#present? の危険性

に公開

何が起こったか

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


DatadogのLATENCYではなかなか見ないm(分)という単位

調べてみると、ActiveSupport で定義されている present? メソッドを使って、画像の有無の確認をしている(無い場合はデフォルト画像を出すような)部分で、問題が発生していました。

原因

プロダクトで使っているRailsをアップデートする作業の中で carrierwave という gem のバージョンを上げていました。
CarrierWave は、Ruby on Rails でファイルアップロードを簡単に扱うための gem です。

そのアップデート内容に 3.1.0Add CarrierWave::Storage::Fog::File#empty? の実装が含まれていました。

https://github.com/carrierwaveuploader/carrierwave/releases/tag/v3.1.0

以下が empty? が入ったCommitです。

https://github.com/carrierwaveuploader/carrierwave/commit/2d1af56cd798da65b16b2285f67ef049f0af8c35

なぜ present? で問題が発生するのか

以下のように ActiveSupport の present? メソッドは blank? メソッドの否定を返します。
そして blank? メソッドは empty? メソッドがあればそれを使う実装になっています。

https://github.com/rails/rails/blob/2ca26346a563a375277e97a09d879df33682df55/activesupport/lib/active_support/core_ext/object/blank.rb#L18-L27

今までは empty? メソッドが無かったため blank? メソッドでファイルの存在確認だけして false が返り、その否定なので present? メソッドは true になっていました。
しかし、今回の変更により empty? メソッドが新たに実装されたことで present? メソッドを呼ぶとその処理が実行されるようになったのです。
結果、empty?メソッドの実装がアップロードされているファイルのサイズが0かどうかまで確認するものだったために、クラウド上のストレージまでHEADでアクセスすることになりました。

そして数十枚の画像を表示する画面で、一枚一枚の画像でクラウド上へHEADのアクセスが走ることになり、大きなレイテンシが発生してしまった、というわけです。

現在 gem での対応が、以下のIssueで検討中のようです。

https://github.com/carrierwaveuploader/carrierwave/issues/2776

問題への対応

present? メソッドを使わないようにしました。
具体的には url メソッドはファイルが無い場合には nil を返すので、その結果を nil? を使って判定するようにしました。

- image_object.present?
+ image_object.url.nil?

まとめ

Railsアプリのバージョンアップ作業で、何かのObjectに empty? が新しく実装されたら、ちょっと気を付けましょう!
present?blank? は便利で色んなところで使いがちですが、こういう問題が発生した際は nil? などで回避できるかも知れませんので、試してみてください。

Livesense Engineers

Discussion