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

ネットの海の片隅で

技術ネタの放流、あるいは不法投棄。

net::ERR_INCOMPLETE_CHUNKED_ENCODING with Nginx

起こったこと

Rails/Unicorn/Nginxという構成で動いているアプリケーションを使っていたら、net::ERR_INCOMPLETE_CHUNKED_ENCODINGというエラーが発生し、32KB以上のページが途中で途切れるようになりました。

f:id:s_osa:20141206173712p:plain

f:id:s_osa:20141206173643p:plain

解決策 その1

Railsのログを追ってみたところ特に異常なし。Unicornも特に異常はなく、試しにNginxを介さずにUnicornに直接アクセスするとページが切れずに表示されました。したがって、犯人はNginx。 そこで、いろいろとググってみたところ、こんなものを発見。

git - Enabling nginx Chunked Transfer Encoding - Server Fault

というわけで、Nginxの設定ファイルに追記します。*1

location / {
  proxy_buffering off;
}

細かいことを気にしないなら、これでおしまいです。

proxy_bufferingディレクティブとは何か

公式ドキュメントによると、

  • Syntax: proxy_buffering on | off;
  • Default: proxy_buffering on;
  • Context: http, server, location
  • Enables or disables buffering of responses from the proxied server.

簡単に言うと、

ONの場合

Nginxはプロキシ先のサーバからのレスポンスをメモリ上のバッファに溜め込みます。なお、バッファのサイズがproxy_buffer_sizeディレクティブ及びproxy_buffers directivesディレクティブで定義されているサイズより大きくなった場合にはディスク上の一時ファイルに書き出します。

OFFの場合

Nginxはプロキシ先のサーバからのレスポンスをクライアントに即時に送り返します。Nginxがプロキシ先のサーバから一度に受け取れるデータの大きさはproxy_buffer_sizeによて定義されます。

もう一歩踏み込む

ここで、proxy_buffersディレクティブについてもう少し踏み込んでみます。

同じく公式ドキュメントによると、

  • Syntax: proxy_buffers number size;
  • Default: proxy_buffers 8 4k|8k;
  • Context: http, server, location

proxy_buffersディレクティブはプロキシバッファの数と大きさを定義します。そして、バッファ数のデフォルト値が8、バッファサイズのデフォルト値がプラットフォームのページサイズです。 今回不具合が起こったアプリケーションはAmazon Linuxで稼働していたため、ページサイズは

$ getconf PAGE_SIZE
4096

ということで、4KBでした。つまり、

4KB × 8 = 32KB

ということで、プロキシ用に32KBのバッファが用意されていることになります。

32KBしかブラウザに落ちてこない理由の一端はこのあたりにありそうです。

なぜ一時ファイルが使われなかったのか

しかし、先ほどproxy_bufferingディレクティブのところで説明したとおり、Nginxはバッファ用メモリが足りなくなると、ディスク上に一時ファイルを作成してそこにバッファリングします。まあ、当然の仕様だと思います。

では、なぜ一時ファイルが使用されなかったのか。答えは簡単です。

「Nginxが一時ファイルを置くディレクトリのパーミッションを持っていなかったから」

はい、死ぬべきですね。すみません。

解決策 その2

というわけで、より良い解決策は

  • Nginxの作業用ディレクトリに適切なパーミッションを設定する
  • 必要に応じてバッファメモリのサイズを大きくする

ということになると思います。つまり、Amazon Linuxの場合だと、

$ sudo chown -R nginx:nginx /var/lib/nginx
location / {
  proxy_buffers 64 4k;
}

という感じですかね。

*1:locationの下じゃなく、serverやhttpの下でもOKです。