Недавно столкнулся с интересной проблемой, когда попытка отдать большой файл через
Исследование показало, что Sinatra сама читает и отдает файл кусками по 512 байт, но web-сервер thin (а также WEBrick) буферизует вывод в оперативной памяти на своем уровне, что и приводит к таким печальным последствиям.
Для решения проблемы достаточно оказалось перейти на web-сервер Rainbows (web-сервер, базирующийся на коде unicorn, но предназначенный для работы без проксирования, для медленных клиентов и/или сервисов). Но при отдаче больших файлов процесс кушал порядка 30% CPU на одном ядре.
Rainbows позволяет оптимизировать отдачу файлов, используя, к примеру, гем sendfile, который предоставляет соответствующие API операционной системы. Но для этого необходимо, чтобы отдача файла шла через Rack::File API.
В текущей master-ветке Sinatra метод send_file переписали, используя API Rack::File, поэтому мы можем просто бэкпортировать соответствующий функционал в существующие версии гема Sinatra:
При этом файл конфигурации rainbows будет выглядеть примерно так:
Теперь мы используем эффективную технику отдачи файлов, если в системе установлен гем rack версии 1.3 или выше, и установлен гем sendfile. Кстати, при использовании ruby 1.9 гем sendfile, скорее всего, не потребуется.
P.S.: Если ваш сервис находится за прокси-сервером, то более оптимально использовать возможности, предоставляемые прокси-серверами, к примеру, API X-Accel-Redirect (nginx) или X-Sendfile (Lighttpd, Apache).
Sinatra::Helpers.send_file приводила к отжиранию всей оперативной памяти (типичный размер файла — 14Gb).Исследование показало, что Sinatra сама читает и отдает файл кусками по 512 байт, но web-сервер thin (а также WEBrick) буферизует вывод в оперативной памяти на своем уровне, что и приводит к таким печальным последствиям.
Для решения проблемы достаточно оказалось перейти на web-сервер Rainbows (web-сервер, базирующийся на коде unicorn, но предназначенный для работы без проксирования, для медленных клиентов и/или сервисов). Но при отдаче больших файлов процесс кушал порядка 30% CPU на одном ядре.
Rainbows позволяет оптимизировать отдачу файлов, используя, к примеру, гем sendfile, который предоставляет соответствующие API операционной системы. Но для этого необходимо, чтобы отдача файла шла через Rack::File API.
В текущей master-ветке Sinatra метод send_file переписали, используя API Rack::File, поэтому мы можем просто бэкпортировать соответствующий функционал в существующие версии гема Sinatra:
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
При этом файл конфигурации rainbows будет выглядеть примерно так:
# try to use sendfile when available begin require 'sendfile' rescue LoadError end Rainbows! do use :ThreadSpawn end
Теперь мы используем эффективную технику отдачи файлов, если в системе установлен гем rack версии 1.3 или выше, и установлен гем sendfile. Кстати, при использовании ruby 1.9 гем sendfile, скорее всего, не потребуется.
P.S.: Если ваш сервис находится за прокси-сервером, то более оптимально использовать возможности, предоставляемые прокси-серверами, к примеру, API X-Accel-Redirect (nginx) или X-Sendfile (Lighttpd, Apache).
