ALB が前にいれば Puma の前段に Nginx は要らない(Slow client 対策の昔と今)
まとめ
- AWS の ALB は 2016 年のローンチ時点から リクエストをバッファリングしている。Slow client 対策で Puma の前に Nginx を置く必要はない
- Rails コミュニティで長年「Puma の前に Nginx」と言われてきたのは、オンプレや HAProxy 直結構成での話。AWS + ALB ではそのまま当てはまらない
- Puma 5(2020)以降、Puma 自体の Slow client 耐性も改善されている
- 自分のチームでは Passenger を使っているので Nginx が同居しているが、これは Slow client ではなく Passenger が Nginx モジュールとして動くから が本質的な理由
5 年前の誤認識
参考記事を読んで、自分が長年持っていた認識が古かったことに気づいた。
5 年ほど前、別プロダクトで Rails アプリを Puma で動かす構成を組んでいたとき、自分は「ALB の前段に Nginx は要りますか?」と聞いた。返ってきた答えは「Slow client 対策で必要です」。それ以来、ずっとそう信じてきた。
でも、これは AWS + ALB 文脈では正確じゃない。
Slow client 問題とは
Web サーバの worker(プロセス or スレッド)は、リクエストを完全に受け取るまで他のリクエストを処理できない。ここで悪意なくとも遅い回線のクライアントが繋いでくると、リクエスト全体を送り終わるまで worker が拘束される。Slowloris 攻撃も同じ原理で、わざとゆっくり送ることで worker を枯渇させる。
Puma は thread + process のハイブリッドモデルで動く。Slow client が thread を専有すると、その分だけ処理能力が落ちる。だから「Puma の前に Nginx を置いてバッファリングさせ、Puma には完全なリクエストだけを渡す」という構成が定石とされてきた。
ALB が最初からバッファリングしている
ところが ALB は 最初から リクエストを完全に受け取ってから target に流す挙動を持っている。AWS のドキュメントにも書いてある。
The load balancer accepts and buffers incoming requests, holding them until the targets are ready to process them.
つまり ALB が前にいる限り、Slow client のリクエストは ALB 側で吸収されて、Puma には完成形のリクエストだけが届く。これは ALB の新機能じゃなくて、プロキシ型 L7 ロードバランサーの本質的な性質。前身の Classic ELB(2009 年〜)も同じだった。
なので「Nginx が無いと Slow client にやられる」という前提は、
- ELB/ALB が無い時代(オンプレ、ベアメタル LB)
- L4 パススルー(NLB / TCP モードの LB)の構成
このどちらかで成立する話で、AWS + ALB なら最初から不要だった。
Puma 自体も改善されている
ついでに調べたら、Puma 5(2020)以降は Slow client 耐性自体も向上している。
queue_requestsのデフォルト挙動が改善first_data_timeoutで「データを送ってこない接続」を切れるpersistent_timeoutで keep-alive のアイドル時間を制御できる
なので「Puma 単体でも昔ほど脆くない」+「ALB が前にいればさらに守られる」の二重で、Slow client 問題は実質ほぼ気にしなくていい状態になっている。
じゃあ Nginx は要らないか
Slow client 対策としては不要。じゃあ何のために Nginx を残すかというと、候補は次のあたり。
- 静的アセット配信(
/public/assets)を Rails プロセスに通さずに返したい - gzip/brotli 圧縮(ALB は圧縮しない)
- Passenger を動かすため
ただ、1 はかなり弱い。現代の Rails 本番構成では静的アセットは普通 S3 + CloudFront に寄せる。precompile したアセットを S3 にアップして、CloudFront から配信して、Rails も Nginx も静的ファイルを触らない、というのが標準。Nginx で静的配信する理由は、CDN を使えない/使いたくない事情がある時(オンプレ、社内ネットワーク内のみ、認証付き静的ファイルで X-Accel-Redirect を使いたい等)に限られる。
2 も似たような話で、CloudFront を挟めば gzip/brotli はそっちでやってくれる。ALB を直接叩く構成で、なおかつアプリ側の Rack::Deflater も使いたくない場合に Nginx が候補に上がる程度。
つまり、CDN を素直に使う構成だと 1 も 2 もほぼ消える。残るのは 3 の Passenger を動かすため だ。自分のチームの本番 Rails アプリは ALB → Nginx → Passenger → Rails で動いていて、ここで Nginx が居るのはまさにこの理由。Slow client でも静的配信でもなく Passenger が Nginx モジュールとして動くから だ。Nginx だけ抜くという選択肢が無い。
つまりこの構成で Nginx を外したいなら、議論の対象は「Nginx vs ALB」じゃなくて「Passenger を Puma に置き換えるか」になる。
学んだこと
技術記事を読んで、自分の中の常識を 5 年ぶりにアップデートできるのは気持ちがいい。
「常識」になっている技術判断は、いつ・どんな前提で出てきたものか、定期的に問い直したほうがいい。前提が変わっていたり、自分の環境では最初から当てはまらなかったりする。今回みたいに「AWS 文脈では最初から不要だった」というケースもある。
あと、何かを聞かれて「○○のために必要です」と答えるとき、その理由が今でも有効か、自分の環境で本当に効くのかを確認する癖をつけたい。受け売りで答え続けると、5 年後にこういう恥ずかしい思いをする。