この記事は、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-Sendfile
やX-Accel-Redirect
ヘッダーを用いるとRailsで認証を行い、nginxがファイルを送るといったことも可能です。