Heroku、私はあなたをストリーム!


問題:CSVまたは他の形式としてエクスポートされたコンテンツのタイムアウト.
これは、CSV、XLSX、PDFなどのような形式でページにエクスポート機能を持っている非常に一般的な機能です.
これは以下のような単純なものです.
def index
  @records = ...
  respond_to do |format|
    format.html # index.html
    format.csv { some_csv_generation_code }
  end
end
しかし、ある日、ユーザーが多くの情報をダウンロードしようとすると、エラーが発生し始めます.
サーバは一定量の秒後にタイムアウトエラーに応答し、ファイルを生成しているリクエストを停止します.
paasのようなherokuでは、この時間制限は、生成された内容の30秒である.また、出力の各々の新しい断片の間に55 ''の別のタイムアウトがあります.公式情報を引用する

An application has an initial 30 second window to respond with a single byte back to the client. However, each byte transmitted thereafter (either received from the client or sent by your application) resets a rolling 55 second window. If no data is sent during the 55 second window, the connection will be terminated.


RailsはActionController ::LIVEでこの種の出力生成を処理するためにストリーミング機能を持っているようです.また、Rails Edge(将来のV 7)には、この機能を達成するように見えるCount - sendSense Streamメソッドがあります.したがって、私はコントローラの懸念に私の柵5.2 APPにそれをコピーして、CSV形式の論理を変更可能な各々の方法を実装するためにCSV生成ロジックを変えました.
最後に修正されたヘッダをオーバーライドするまでは動作しませんでした.this issue discussionを参照.
コントローラの完全なコードです.
require 'content_disposition'

module Streamable
  extend ActiveSupport::Concern
  include ActionController::Live

  # Almost verbatim copy of new Rails 7 method.
  # See https://edgeapi.rubyonrails.org/classes/ActionController/Live.html#method-i-send_stream
  def send_stream(filename:, disposition: 'attachment', type: nil)
    response.headers['Content-Type'] =
      (type.is_a?(Symbol) ? Mime[type].to_s : type) ||
      Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete('.')) ||
      'application/octet-stream'

    response.headers['Content-Disposition'] = ContentDisposition.format(disposition: disposition, filename: filename)
      # with rails 6 remove content_disposition gem and use:
      #  ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)

    # Extra: needed for streaming
    response.headers['Last-Modified'] = Time.now.httpdate

    yield response.stream
  ensure
    response.stream.close
  end
end

5 .X私はContent - Dispositionヘッダを設定するために余分な宝石を必要としました.gem 'content_disposition', '~> 1.0' # Not needed on Rails 6遅いダミーデータを生成するコントローラのテスト例
class ExporterController < ApplicationController
  include Streameable

  def index
    respond_to do |format|
      format.html # index.html
      format.js   # index.js
      format.csv do
        send_stream(attachment_opts) do |stream|
          stream.write "email_address,updated_at\n"

          50.times.each do |i|
            line = "pepe_#{i}@acme.com,#{Time.zone.now}\n"
            stream.write line
            puts line
            sleep 1  # force slow response
          end
        end
      end
    end
  end

  private

  def attachment_opts
    {
      filename: "data_#{Time.zone.now.to_i}.csv",
      disposition: 'attachment',
      type: 'text/csv'
    }
  end
end
最後に、curlのようなものを使用すると、2番目目で出力される出力が表示されます.

curl -i http://localhost:3000/exporter



今すぐあなたのアプリケーションのストリームのようなデータを大量にストリームすることができます!
私はHeroku appでこのアプリを展開しているコードはthis github repoにあります.
余分なパフォーマンスのヒント.ActiveRecordを使ってDBからレコードを取得する際には、Count Countの代わりにCount Countを使います.
これがあなたのために役に立つならば、知らせてください.
ハッピーレールハッキング!