Pull to refresh

Большие файлы и Sinatra

Reading time2 min
Views2K
Недавно столкнулся с интересной проблемой, когда попытка отдать большой файл через 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).
Tags:
Hubs:
+23
Comments13

Articles

Change theme settings