Недавно столкнулся с интересной проблемой, когда попытка отдать большой файл через
Исследование показало, что 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).