Sinatra::Helpers.send_file
を介して大きなファイルを提供しようとすると、すべてのRAMが消費されるという興味深い問題に遭遇しました(一般的なファイルサイズは14Gbです)。
この調査では、 Sinatra自体がファイルを512バイト単位で読み取り、提供しますが、シンWebサーバー(およびWEBrick)は独自のレベルでRAMに出力をバッファリングするため、このような悲しい結果につながります。
この問題を解決するには、 Rainbows Webサーバー(ユニコーンコードに基づくWebサーバーですが、遅いクライアントやサービスに対してプロキシなしで動作するように設計されています)に切り替えるだけで十分です。 ただし、大きなファイルを送信する場合、プロセスは1つのコアでCPUの約30%を消費しました。
Rainbowsでは、たとえばsendfile gemを使用して、 ファイルのアップロードを最適化できます。sendfilegemは、オペレーティングシステムに適切なAPIを提供します。 ただし、このためには、ファイルのアップロードがRack :: File APIを経由する必要があります。
現在のSinatraマスターブランチでは、send_fileメソッドはRack :: File APIを使用して書き換えられたため、対応する機能をSinatra gemの既存のバージョンに簡単にバックポートできます。
if Sinatra::VERSION < '1.3.0' && Rack.release >= '1.3' # Monkey patch old Sinatra to use Rack::File to serve files. Sinatra::Helpers.class_eval do # Got from Sinatra 1.3.0 sources def send_file(path, opts={}) if opts[:type] or not response['Content-Type'] content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' end if opts[:disposition] == 'attachment' || opts[:filename] attachment opts[:filename] || path elsif opts[:disposition] == 'inline' response['Content-Disposition'] = 'inline' end last_modified opts[:last_modified] if opts[:last_modified] file = Rack::File.new nil file.path = path result = file.serving env result[1].each { |k,v| headers[k] ||= v } halt result[0], result[2] rescue Errno::ENOENT not_found end end end
虹の構成ファイルは次のようになります。
# try to use sendfile when available begin require 'sendfile' rescue LoadError end Rainbows! do use :ThreadSpawn end
gemラックバージョン1.3以降がシステムにインストールされ、sendfile gemがインストールされている場合、効果的なファイルアップロード手法を使用します。 ところで、ruby 1.9を使用する場合、sendfile gemはほとんど必要ありません。
PS:サービスがプロキシサーバーの背後にある場合、プロキシサーバーによって提供される機能、たとえばX-Accel-Redirect(nginx)またはX-Sendfile(Lighttpd、Apache)APIを使用する方が最適です。