読者です 読者をやめる 読者になる 読者になる

らっちゃいブログ

日々の学びと気づきを発信するブログ

Nginxのproxy_next_upstreamが意図した挙動にならない問題を調べた

スポンサーリンク

どうもこんにちは。racchai です。

Nginxの設定が意図通りの動きにならなくていろいろ調査したので、共有したいと思います。

proxy_next_upstream とは

Nginx を複数サーバー分散構成のロードバランサー(リバースプロキシ)として動作させる際に指定するディレクティブです。

バックエンドサーバーとの通信を行い、何が起きたら次のサーバーへリトライするかを定義するというものになります。

例えば、レスポンスが 500 エラーだったら次のサーバーへリトライというような設定をすることが可能です。

何が問題なのか

proxy_next_upstream: error という設定に問題がありました。

これはバックエンドサーバーとの通信中にエラーが発生した場合はリトライをするという設定になります。

ドキュメントを読んでみると「接続確立時、リクエストの送信時、そしてレスポンスヘッダーの読み込み時のエラー」がリトライ対象とあります。

an error occurred while establishing a connection with the server, passing a request to it, or reading the response header;

しかし、レスポンスヘッダー読み込み時のエラーはリトライの対象としてもらっては困ります。レスポンスを返しているということは、すでにリクエストが処理済みということですから、それがコメント投稿リクエストだとすれば二重投稿になってしまいます。

これはなんとかしなければいけません。

proxy_request_buffering を無効にしてみる

nginx のドキュメントをさらに読み進めると、proxy_request_buffering の項目に以下のような記載がありました。

When buffering is disabled, the request body is sent to the proxied server immediately as it is received. In this case, the request cannot be passed to the next server if nginx already started sending the request body.

リクエストのバッファリングを無効化した場合、ブラウザから受信したデータはただちに中継先サーバーへ送信するようになる。リクエスト情報を中継し始めたリクエストは次のサーバーへリトライされない。

なるほど、これなら意図した動作になりそうということで、さっそく proxy_request_buffering を無効化してみました。が、結果は変わらずでした。

正確には、データサイズによってリトライされたりされなかったりという挙動です。データサイズがある程度大きいとリトライされません。

調べていくと、どうやらリクエストバッファリングを無効化しても、内部的にバッファリングされてしまう条件があることがわかりました。

Nginx は proxy_request_buffering の設定に関わらずリクエストヘッダーをバッファリングしています。そして問題はその動作の中にあります。

Nginx はリクエストヘッダーを読み込むためのバッファを持っており、リクエストヘッダーを読み切ってもまだバッファに空きがある場合、Nginx は可能な限りボディまで先読みしてしまうようです。

リクエストボディが小さいとバッファに収まってしまうため、proxy_request_buffering を無効化しても有効化しているときと同じ動作となってしまい、結果としてリトライされてしまうという話のようです。

なるほど、それならリトライされたりされなかったりという現象も説明がつきそうですね。

というわけで proxy_request_buffering では解決できませんでした。

解決方法

結論からいいますと、Nginxのバージョンを 1.9.13 に上げるだけで、ほぼ理想とする動作となることがわかりました。(元々使っていたのは1.7.12)

これだけ時間かけて調べたのにマジかよって感じです。

リリースノートにはこう記載されています。

Change: non-idempotent requests (POST, LOCK, PATCH) are no longer passed to the next server by default if a request has been sent to a backend; the "non_idempotent" parameter of the "proxy_next_upstream" directive explicitly allows retrying such requests.

デフォルトで非冪等なメソッド(POST, LOCK, PATCH)はリトライされないようになったそうです。

なんてタイムリーな仕様変更なんでしょう。というかこれBugfixじゃないんですかね。。

まとめ

Nginxは便利ですが意図した挙動にならない(しかもそれが仕様である)ことも多いので、しっかりと動作確認しましょう。

期待動作にならなくて行き詰ったら、リリースノートにそれっぽい変更がないか探してみましょう。

nginx実践入門 (WEB+DB PRESS plus)

nginx実践入門 (WEB+DB PRESS plus)