Rails: Rack::Deflaterの条件付きGZIP圧縮でレスポンスサイズを劇的に削減(翻訳)
"rack deflater path condition"や"rack deflater if option"でググってこの記事にたどり着いた方へ: 以下がお求めの答えです。
config.middleware.use(
Rack::Deflater,
:if => lambda do |env, _, _, _|
env["PATH_INFO"] == "/エンドポイントへのパス"
end
)
上のコードをapplication.rb
に追加してください。以上。
ここで本記事を終えてもいいのですが、ネットで拾ったいい加減な情報を鵜呑みにしてはいけないことは常識です。どんなに最先端のAI搭載検索エンジンがそれっぽい回答を提案したとしても、よくわからないままピザソースに無害な糊を練り込むのはよろしくありません。
🔗 背景情報
最初に、この構成にたどり着くまでの経緯を説明しておきましょう。
最近の私たちは、プロジェクトで(UI的に)最も複雑で込み入ったページのパフォーマンス改善に取り組んでいます。そのページには2つのテーブルと1個のグラフがあるだけですが、テーブルが数千個のセルを含むレベルで巨大です。これを「Hotwire化」すると巨大なHTMLになります 😉😉。「JSONにすればデータ量が減るのでは?」とお思いかもしれませんが、私はHotwireの仕組みが気に入っているので、ハンドブックにある以下の説明に完全に賛成しています。
もちろん、HTMLペイロードは同等のJSONよりサイズが多少大きくなる可能性もありますが、gzip圧縮すれば通常なら無視できるほど差は小さくなり、クライアント側でJSONを取得してHTMLに変換する作業を行わずに済みます。
Turbo Streams handbookより
そして、私たちの実際の環境はそうなっていませんでした 😉。ある時期から、インターネット接続でサーバーを待つ時間よりも、レスポンスのダウンロードにかかる時間の方が長くなっていることに気づいたのです。
ここから「レスポンスサイズが巨大になっていること」「Content-Encoding
ヘッダーがないこと」「ダウンロード時間」が確認できます。クライアントはAccept-Encoding: gzip, deflate, br, zstd
ヘッダーを送信していたので、念のためcURLでダブルチェックしてみました。cURLでダウンロードサイズを確認するには、--write-out
オプションにsize_download
変数を渡します。
ダウンロードしたコンテンツのサイズは、Accept-Encoding
ヘッダーありのリクエストと、Accept-Encoding
なしのリクエストのどちらの場合でも同じでした。
「会社としては、この絵とこの絵がどう違うかを調べてもらいたいんだよ」
「どっちも同じでしょ?」
つまり、サーバーがデフォルトで圧縮をサポートしていないということです。このプロジェクトは標準的なHerokuセットアップ上でホスティングされているので、(圧縮機能が設定済みの)リバースプロキシ的なものはありません。Herokuのドキュメントによると、このような場合はアプリケーション側で圧縮するのだそうです。
このプロジェクトについて少々免責事項を述べておきますと、私たちはページ要素をピンポイントで精密に更新しているので、レスポンスの圧縮が必要なケースはほんのわずかです。サイズの小さなコンテンツで圧縮・解凍を行う意味はありません。私たちが普段Hotwireをどのように活用しているかについては、以下のYouTube動画をご覧ください。
しかも、アセットのgzipは無効にしてある(config.assets.gzip = false
)ので、CDNでより適切に処理されます。これについては以下の過去記事にも書きました。
参考: Don't waste your time on assets compilation on Heroku | Arkency Blog
🔗 Rack::Deflater
を採用した理由
私たちに与えられた条件と納期を考慮すると、このプロジェクトではRack::Deflater
を条件付きで有効にするのがよい選択だと考えました。このソリューションによって、最大5MBだったレスポンスサイズを最大100KBにまで削減できました 😉。非常に満足のいく結果を得られたので、この方法を試してみることに決めたのです。
圧縮の必要なエンドポイントが増えすぎない限り、この方法をおそらく使い続けるでしょう(私たちのTurbo FramesやTurbo Streamsの使い方↓では、そうなる可能性は低いでしょう)。
🔗 検討材料
長期的には、レスポンスサイズのメトリクスを収集して、たとえばレスポンスがどんなサイズになったら圧縮が必要かというスイートスポットを見つけることも可能でしょう。そうなったら、heroku-buildpack-nginxなどで圧縮処理の部分を(nginxなどの)リバースプロキシに移動すればよいでしょう。
これでアプリ自身が圧縮のためにリソースを消費する必要もなくなり、特殊な圧縮ツールを使えば、たとえばGZIPに加えてBrotilで圧縮することも可能になるでしょう。
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。
参考: Class:
Rack::Deflater
— Documentation for rack (3.1.4)