Edited at
nginxDay 5

Rails + unicorn で nginx-upload-moduleを使ってみた

More than 3 years have passed since last update.

この記事は、nginx Advent Calendar 2015の5日目の記事です。


nginx-upload-module

nginxでは、クライアントから multipart/form-data でファイルのアップロードを始めると /tmp 配下に一時ファイルを作ります。nginxは、クライアントがファイルをアップロードし終わると、ミドルウェアに一時ファイルからreadして受け渡します。

この仕組みだと、サーバ内で同じファイルを何度もコピーし直すこととなり、数MBのファイルでは問題ありませんが、GB単位のファイルになるとクライアントがタイムアウトしたり、ディスクの容量が足りなくなったりと問題が生じてきます。

これを解決するのが nginx-upload-moduleです。nginx-upload-module はnginxでファイルを取得し、そのパスやファイル名などをミドルウェアに渡します。ファイルそのものではなくパスやファイル名のみを渡すので、サーバ内でのファイルの受け渡しがなくなり、前述のような問題が起きません。

この nginx-upload-module ですが、2008年から更新は止まっていて、現在の nginx で使えるかどうかは書いてありませんが、今回は nginx-1.9.6 で試してみました。

https://github.com/vkholodkov/nginx-upload-module

今回やっていること&目的は以下のブログに書いてあることとほぼ同じですので、基本ここを参考にしていきます。この記事では、この設定ではまったことなどを書いていきます。


Railsで大きなファイルを扱う際のポイント

http://techracho.bpsinc.jp/baba/2014_10_08/19139



環境


  • CentOS 6.7

  • nginx 1.9.6

  • nginx-upload-module 2.2.0


ダウンロードとコンパイル

必要なソースコード等をダウンロードし、--add-module オプションを付けてnginxをコンパイルします。

$ git clone -b release-1.9.6 https://github.com/nginx/nginx.git

$ git clone https://github.com/vkholodkov/nginx-upload-module.git'
$ cd nginx
$ auto/configure (略) --add-module=../nginx-upload-module"
$ make -j4
$ sudo make install


設定

今回は、以下のような設定をしました。

    location / {

try_files $uri @application;

if ($args ~ ngx_upload_module=on ) {
upload_pass @application;
upload_pass_form_field ".*";
upload_store /tmp;
upload_store_access user:rw group:rw all:rw;
upload_resumable on;
upload_set_form_field "$upload_field_name[filename]" "$upload_file_name";
upload_set_form_field "$upload_field_name[type]" "$upload_content_type";
upload_set_form_field "$upload_field_name[tempfile]" "$upload_tmp_path";
upload_aggregate_form_field "$upload_field_name[md5]" "$upload_file_md5";
upload_aggregate_form_field "$upload_field_name[size]" "$upload_file_size";
upload_cleanup 200-599;
}
}

どのようなオプションが設定できるかは、参考の記事や 公式のドキュメント を見ると良いです。

ここでは、特に注意したいところについて書きます。


引数でモジュールの使用をスイッチする

参考の記事や多くの情報では、nginxの設定に nginx-upload-module を使いたいパスのlocationを書けというのが多いですが、これだと汎用性が低く、他のリソースにも適用したい時に不便です。

なので、if文を用いてURLにngx_upload_module 引数が付いている場合のみ、nginx-upload-moduleを使用します。これにより、新しく対応したいリソースが増えた場合にもnginxの設定を触る必要はありません。

この場合、Rails側のビューファイルも変更してあげる必要があります。

form_forを利用している場合は、urlオプションを使って、パラメーターを渡してあげます。

= form_for @upload, url: uploads_path(ngx_upload_module: 'on') do |f|


自動的にファイルを削除する

今回作ったアプリケーションでは、サーバが受け取ったあとはGoogle Cloud Storageにアップロードされるため、サーバ内に保存しておく必要がありません。

nginx-upload-moduleはそのまま使用するとファイルを残してしまうので、あっという間にディスクがいっぱいになってしまいました。

最初は試行錯誤していたのですが、upload_cleanupというオプションがあり、ステータスコードを指定することでそのステータスコードの場合のみ削除できるようです。

本来は、エラー時などに消す目的のようですが、今回はアップロードファイルは全て削除したいので、以下のように 200-599 と指定して常に削除するようにします。

upload_cleanup 200-599;


コントローラー

ほぼ参考記事と同じで、リファクタリングをした程度なので割愛します

ちなみに、開発環境やnginx-upload-moduleを使っていない場合でも動作するようになっています

  def upload_params

params.require(:uploads).permit(:name).merge(file: upload_file, upload_filename: upload_filename)
end

def upload_file
file = params[:uploads][:file]
if file.is_a?(Hash) && file[:tempfile]
file[:tempfile]
else
file.path
end
end

def upload_filename
file = params[:uploads][:file]
if file.is_a?(Hash) && file[:filename]
file[:filename]
else
file.original_filename
end
end


まとめ

nginx-upload-moduleを用いて、アップロードファイルを効率良く処理する方法でした。

正直、nginx-upload-moduleがどれだけメンテされてるかわからないですし、GB単位の大きいファイルをアップロードさせる必要がある場合のみ対処すれば良いと思います。

今回はアップロードの話でしたが、ダウンロードの場合は、X-SendfileX-Accel-Redirectヘッダーを用いるとRailsで認証を行い、nginxがファイルを送るといったことも可能です。