はじめに
Rails初心者の方が書いたActiveStorageを利用するコードをレビューしていると、結構な頻度で以下のようなコードを見かけます。
<%= image_tag user.avatar.variant(:thumb).processed %>
ここでみなさんに質問です。
最後に付けている processed
ってどういう役割があるのか知っていますか?
結論からいうと、大半のケースで processed
を付ける必要はありません。
本記事ではその理由を説明します。
processed は何をするのか?
processed
メソッドは以下のような働きをします。
- バリアント画像が未生成なら、その場で生成する
- 既に生成済みなら、それを返す
つまり、processed
を付けると、view(erb)をHTMLに変換するタイミングでバリアント画像が生成されます。
processed を付けなかったらどうなるのか?
では、以下のように processed
を付けなかった場合はどういう挙動になるのでしょうか?
<%= image_tag user.avatar.variant(:thumb) %>
この場合は、遅延処理でバリアント画像が生成されます。
具体的には以下のような動作になります。
- HTML生成時はバリアント画像を生成せず、バリアント画像用のURLだけを生成する
- ブラウザはサーバから返ってきたHTMLを読み込む
- ブラウザはimgタグのsrc属性に書かれたURLに対して、画像を返すようにリクエストする
- サーバ(Rails)はバリアント画像が未生成なら、リクエストが飛んできたタイミングで生成し、その画像を返す。すでに生成済みなら、それを返す。
processed を付けると画面表示が遅くなる!
processed
を付けたときと付けないときの違いは上で述べたとおりです。
ですが、これだけ聞いても「ふーん……。だから何?」と思っている人も多いのではないでしょうか?
しかし、この違いはwebアプリケーションのパフォーマンスに大きな影響を与えます。
ここでは例として、大量のアバター画像を表示するページを考えてみます。
<!-- usersテーブルには100人分のデータが格納されていると想定 -->
<% User.all.each do |user| %>
<%= image_tag user.avatar.variant(:thumb).processed %>
<% end %>
この場合、Railsアプリケーションは次のような動きになります。
- 1人目のユーザーアバターのバリアント画像を生成する (0.1秒)
- 2人目のユーザーアバターのバリアント画像を生成する(さらに0.1秒)
- 3人目のユーザーアバターのバリアント画像を生成する(さらに0.1秒)
- 4人目の……(略)
- 99人目のユーザーアバターのバリアント画像を生成する(さらに0.1秒)
- 100人目のユーザーアバターのバリアント画像を生成する(さらに0.1秒)
- ブラウザに生成したHTMLを返す
つまり、バリアント画像の生成に0.1秒かかると仮定した場合、全員分のバリアント画像を生成するのに、
0.1秒 x 100人 = 10秒
かかることになります。
このwebアプリケーションの利用者はユーザー一覧ページにアクセスしたら、サーバからレスポンスが返ってくるまで「ページを読み込み中です」の表示を10秒以上見続けることになります。
processed を付けなければ即座にレスポンスが返る
一方、processed
を付けなかった場合はこうなります。
<!-- processed を付けずにバリアント画像を表示する -->
<% User.all.each do |user| %>
<%= image_tag user.avatar.variant(:thumb) %>
<% end %>
- 1人目のユーザーアバターのバリアント画像のURLのみを生成する (ほぼ0秒)
- 2人目のユーザーアバターのバリアント画像のURLのみを生成する (ほぼ0秒)
- 3人目のユーザーアバターのバリアント画像のURLのみを生成する (ほぼ0秒)
- 4人目の……(略)
- 99人目のユーザーアバターのバリアント画像のURLのみを生成する (ほぼ0秒)
- 100人目のユーザーアバターのバリアント画像のURLのみを生成する (ほぼ0秒)
- ブラウザに生成したHTMLを返す
バリアント画像を実際に生成する処理に比べると、バリアント画像のURLのみを生成するコストはかなり小さいため、100人分のデータがあってもほとんど時間がかかりません。
よって、ユーザー一覧ページにアクセスすると一瞬でサーバからレスポンスが返ってきます。
ただし、実際のバリアント画像はimgタグ経由でサーバにアクセスがあったタイミングで生成されるため、ユーザーはパラパラと時間をかけて画面上に表示されていくアバター画像を眺めることになります。
イメージ的にはこんな感じ
実際のブラウザの動きをアニメーションgifで動画にしてみました。
ユーザーの視点に立ったとき、嬉しいのはどちらでしょうか?
processedを付けたとき
バリアント画像の生成が全部終わってからHTMLがサーバから返ってくるため、ユーザー一覧ページを開くのに時間がかかります。
processedを付けないとき
バリアント画像生成は後回しにされるので一瞬でユーザー一覧ページが開き、そのあとに画像が読み込まれていきます(画像読み込みのタイミングでバリアント画像が生成される)。
その他、知っておくとよい知識
一度バリアント画像が作られたら速度はあまり変わらない
processed
を付けたときも付けなかったときも、一度バリアント画像が作成されたら2回目以降は作成済みのバリアント画像が再利用されるので、速度はあまり変わりません。
速度の違いが出るのは、バリアント画像が未作成だった場合です。
とくに、バリアント画像未作成の画像ファイルを1ページ内にたくさん表示する場合に速度の違いが顕著になってきます。
processed が必要なケースってどんなとき?
基本的に processed
をわざわざ使うことは滅多にないと思っているのですが、実際の利用例があまり思いつかないのでChatGPTに聞いてみました。
ChatGPTいわく、
画像のメタデータが必要なとき
生成済みのバリアント画像の幅や高さを取得する場合など。variant = user.avatar.variant(resize_to_limit: [800, 800]) variant.processed.metadata[:width]
処理完了を同期的に保証したいとき
ジョブ内やAPIレスポンス生成時に「確実に今作っておきたい」場合などです。
とのことです。なるほど〜。
とはいえ、これまでの経験を振り返ってもやっぱりこういう使い方が必要だったケースは思いつかないですね
Railsガイドの説明も読んでおこう
processed
メソッドについてはRailsガイドの中でも説明されています。
まだ読んだことがない人は、「8.1 遅延読み込みとイミディエイト読み込み」の項に書かれたRailsガイドの説明もしっかり読んでおきましょう!
まとめ
というわけで本記事ではActiveStorageの processed
の役割を説明し、ほとんどのケースで processed
を付ける意味はないんですよ、という話を書いてみました。
おそらくRails初心者の多くはネットの記事に載っていた間違った情報を盲目的にコピペして、 processed
を付けているんじゃないかと思います。
しかし、Railsガイドのような公式のサンプルコードを見ると、いずれも processed
は付いていません。
Rails初心者の方は盲目的にネット記事のコードをコピペするのではなく、「このメソッドは何のために使われているのか?」「公式サイトではどのように説明されているのか?」といった点もしっかり確認した上で自分のコードに適用するようにしてください!