Rails
nginx

Nginxを使ってファイルアップロードを高速化する方法

More than 1 year has passed since last update.

問題

Webアプリケーションで、大きなファイルのアップロードを受け付けると、リクエストベース(イベントベースじゃない)で作られたWebApplicationでは、 プロセスがその処理にロックされて、他の処理が遅延しパフォーマンスがでなくなります

解決策

ファイルを受け取る 部分をNginxに任せることが出います。
Nginx(イベントベース)のプロセスにファイルのアップロードを担当させて、アップロードしたファイルをApplicationに渡すことができます。ファイルの情報は、ヘッダーで受け取ります。

Before

Client(ファイルをアップロード)
↓
Nginx ()
↓
App   (ファイルを受け取って、保存)

After

Client(ファイルをアップロード)
↓
Nginx (ファイルを受け取って、保存)
↓
App   (保存されたファイルパスとリクエストを受け取る)

設定

NginxにFileUplodaを行うlocationを指定し、保存先を決めます。
さらに、そのリクエストをproxy_passでbackendのApplicationサーバに送ります。

  location /nignxupload {
          limit_except POST          { deny all; }

          client_body_temp_path      /tmp/;
          client_body_in_file_only   on;
          client_body_buffer_size    128K;
          client_max_body_size       1000M;

          proxy_set_header           X-FILE $request_body_file;
          proxy_pass                 http://app_upstream/file/;
  }

この設定で、app_upstremにX-FILEヘッダーにてPATHが指定されたリクエストが送られます。

ApplicationをRailsの場合以下のようにします。

controller
class ApplicationController < ActionController::Base
  def status
    render :text => 'YES'
  end

  def file
    render :text => request.headers['X-File']
  end

  def app_upload
    render :text => params[:file].original_filename
  end
end
config/routes.rb
Rails.application.routes.draw do
  match '/status' => 'application#status', via: [:get, :post]
  match '/file' => 'application#file', via: [:get, :post]
  match '/appupload' => 'application#app_upload', via: [:get, :post]
end

機能評価

条件

Haproxy -> Nginx -> App

apache benchで一定の負荷をかけながら、threadで同時にファイルをアップロードします。
Nginx Upload を叩くURLとApp Upload を叩くURLを用意して比較しました。

## 一定の負荷を常にかける
$ab -n 100 -n 1000000 'http://www.example.com/status'

## スレッドでfileをUploadする
$ruby upload_via.rb nignxupload
$ruby upload_via.rb appupload
upload_via.rb
100.times do
  Thread.new do
    system("curl -H "X-FILE: vagrant" -F file=@README.rdoc  -s -D - http://www.example.com/#{ARGV.first}")
  end
end

の構成で、Newrelicを使って調べてみた。

結果

App Uploadを使った場合は、プロセスがうめられHaproxyで待たされます(Request Queuing)。プロセスが埋まってしまったため、スループットは出なくなります。
一方Nginx Uploadを使った場合は、NginxがUploadの処理をするためAppプロセスは埋まらずに返信し続けれます。

スクリーンショット_2014-04-14_14_53_26.png

Nginx Upload
スクリーンショット 2014-04-14 14.53.58.png

App Upload
スクリーンショット 2014-04-14 14.54.07.png

セキュリティについて

  • Railsでいうと、protect_from_forgeryをOffにして、nginxからのアクセスについて、何かしらの制限をかける必要がある(ex: internal制限をする)
  • uploadされまくらないように、何かしらの制限を考える必要ある(ex: basic auth)

参考

https://coderwall.com/p/swgfvw