search
LoginSignup
16

More than 5 years have passed since last update.

posted at

updated at

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

この記事は、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がファイルを送るといったことも可能です。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
16