rails3.1で大きいデータ(csv等)をストリーミングで出力する方法
はじめに
管理画面等で膨大なデータをcsvで落としたい時等に
そのままDBから文字列を作って返すと反応がないままタイムアウトになると思います。(なんせ全部メモリに落とすのだから)
こういう場合の回避方法(2パターン)
- db等にリクエストをキューイングしておきバッチで処理してzipに圧縮しておく。
- サーバーにはお行儀がいいお作法。ただし工数がかかる。
- 1000件等の細かい単位にしてsqlを発行してストリーミングで出力する。常にブラウザにデータが流れるためタイムアウトしない。
- 工数少なめ。ただしサーバーに通信しっぱなしになるのでサーバーの同時リクエスト数を食いつぶす。管理画面等の一部利用者のみなら問題ないでしょう。
今回2の方法をrails3.1で行う文献が見つからなかったので調べました。
サンプル
1秒おきにtest1,test2..test5のように出力されます。
class TestController < ApplicationController def index headers["Cache-Control"] ||= "no-cache" headers["Transfer-Encoding"] = "chunked" # 上記のheadersを書くかもしくは下記のrenderでもOK。headersのほうが余計な処理が入らないのでお勧めかな?? #render :text=>"", :stream => true # DLにする場合はここのコメントを外せばOK.ブラウザで順次出力されるのを確認できるようにあえて外しておきます。 # headers.merge!( # 'Content-Type' => "text/csv; charset=Shift_JIS", # 'Content-Disposition' => "attachment; filename=\"test.csv\"" # ) self.response_body = Rack::Chunked::Body.new(Enumerator.new do |y| 5.times do |i| y << "test#{i} \n" sleep 1 end end) end end
大抵はDBから1000件ずつ等ちょこちょこ取得して出力するでしょう。その際はfind_eachを使うと便利です。
http://wiki.usagee.co.jp/ruby/rails/RailsGuides%E3%82%92%E3%82%86%E3%81%A3%E3%81%8F%E3%82%8A%E5%92%8C%E8%A8%B3%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F%E3%82%88/Active%20Record%20Query%20Interface#i803e697
注意点
webrickではストリーミングになりません。
unicornを使ってください。
また古いバージョンのunicornだと設定に下記を指定しないといけないそうです。(現在はデフォでこの設定になってる)
# unicorn.config.rb listen 3000, :tcp_nopush => false
参考
rails3.1の新機能のhttpストリーミング部分のソースを読みました。(実際はrailsのbody部分の出力の流れまで追ってしまいました。。unicornのc部分まで行きそうな所で力尽きましたw)
actionpack-3.1.3/lib/action_controller/metal/streaming.rb
- httpストリーミングとは
- httpストリーミングの使い方
- railsオフィシャルドキュメント(英語)