問題
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の場合以下のようにします。
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
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
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プロセスは埋まらずに返信し続けれます。
セキュリティについて
- Railsでいうと、
protect_from_forgery
をOffにして、nginxからのアクセスについて、何かしらの制限をかける必要がある(ex: internal制限をする) - uploadされまくらないように、何かしらの制限を考える必要ある(ex: basic auth)
参考