Search code examples
ruby-on-railsthin

Rails + thin: Not possible to download large files


I have a rails app where users can manage large files (currently up to 15 GB). They have also the possibility to download the stored files.

Everything works perfect for files < 510 MB. But for > 510 MB, the download stops after 522,256 KB (510 MB).

I think thin produces this issue. When I start my dev server using thin, I cannot download the complete file. When I start the dev server using webrick, everything works.

I used top to compare the RAM/CPU behavior, but both server, thin and webrick, behave the same way. In development, both server read the complete file into RAM and then send it to the user/client.

I tried to change some options of send_file like stream, or buffer_size. I also set length manually. But again, I was not able to download the complete file using thin.

I can reproduce this behavior using Firefox, Chrome, and curl.

The problem is that my productive rails app uses 4 thin servers behind an nginx proxy. Currently, I cannot use unicorn, or passenger.

In development, I use thin 1.6.3, rails 4.1.8, ruby 2.1.2.

def download
  file_path = '/tmp/big_file.tar.gz' # 5 GB
  send_file(file_path, buffer_size: 4096, stream: true)
end

Solution

  • If you are using send_file, it is ideal to use a front end proxy to pass off the responsibility of serving the file. You said you are using nginx in production, so:

    In your production.rb file, uncomment config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'.

    You will also have to change your nginx configuration to accommodate Rack::Sendfile. Its documentation is located here. The changes amount to adding:

    proxy_set_header X-Sendfile-Type X-Accel-Redirect;
    proxy_set_header X-Accel-Mapping /=/files/; # or something similar that doesn't interfere with your routes
    

    to your existing location block and adding an additional location block that handles the X-Accel-Mapping that you added. That new location block might look like:

    location ~ /files(.*) {
      internal;             
      alias $1;             
    }   
    

    You will know it is working correctly when you ssh to your production server and curl -I the thin server (not nginx) and see a X-Accel-Redirect header. curl (no -I) directly to the thin server should not send the file contents.

    You can see my recent struggle with nginx and send_file here.