ActiveResourceが遅い→JSONならパースが速いよ
きっかけ
ネットワーク越しだし、速度が出ないのはまぁいいんだけど、それにしたって遅い。
具体的にはXMLのパースが遅い、遅すぎる。
なんとかならぬか。
パーサの速度比較
同じデータをto_jsonとto_xmlでそれぞれシリアライズしたファイルを用意。(20数個のフィールドを持つレコード20件のもの。)
Hash.from_xml(XmlSimple)、ActiveSupport::JSON.decode、JSON.parse(JSON implementation for Ruby)それぞれでパースに必要な時間を測定してみた。
Benchmark.bm do |x| x.report { 10.times{ Hash.from_xml(xml) } } x.report { 10.times{ ActiveSupport::JSON.decode(json) } } x.report { 10.times{ JSON.parse(json) } } end
user system total real 0.870000 0.530000 1.400000 ( 1.394237) 0.160000 0.010000 0.170000 ( 0.168284) 0.000000 0.000000 0.000000 ( 0.006735)
JSON.parse、圧倒的じゃないか・・・
/ ̄ ̄\ / _ノ \ | ( ●)(●) . | (__人__) XmlSimple遅すぎだろ | ` ⌒´ノ 常識的に考えて… . | } . ヽ } ヽ ノ \ / く \ \ | \ \ \ | |ヽ、二⌒)、 \
経過
コントローラ
def index # 省略 respond_to do |format| format.xml { render :xml => @shops } format.xml { render :xml => @shops } end end def create # 省略 respond_to do |format| if @shop.save format.xml { render :xml => @shop, :status => :created, :location => @shop } format.json { render :json => @shop, :status => :created, :location => @shop } else # ActiveRecord::Errors#to_xmlは専用のものでオーバーライドされているが、 #to_jsonはそうでは無く、TypeError: wrong argument type Hash (expected Data)が発生してしまうのに注意。 format.xml { render :xml => @shop.errors, :status => :unprocessable_entity } format.json { render :json => { :errors => @shop.errors.full_messages }, :status => :unprocessable_entity } end end # show/update/destroyも同様に。 end
ActiveResource
ActiveResource::Base.formatを変更する。
class Shop << ActiveResource::Base self.site = 'http://taslam-example.jp/' self.format = :json # ActiveResource::Formats:JsonFormatを使う end
しかし、、
Shop.find(:all).first.name # => "\\u30a2\\u30c9\\u30c7\\u30b6\\u30a4\\u30f3"
日本語がうまくデコードされないみたいだ。
ActiveResource::Formats:JsonFormatを見てみると、
def decode(json) ActiveSupport::JSON.decode(json) end
どうやら、ActiveSupport::JSON.decodeがだめみたい。
試してみると、JSON.parseだとうまくデコードされる模様。
そこで、このActiveSupport::JSONをJSONに置き換えたActiveResource::Formats:ExJsonFormatを作って、こちらを使うようにする。こっちのが速いしね。
# application.rbの末尾などに require 'json' module ActiveResource module Formats module ExJsonFormat include ActiveResource::Formats::JsonFormat def decode(json) JSON.parse(json) end extend self end end end
class Shop << ActiveResource::Base self.site = 'http://taslam-example.jp/' self.format = :ex_json end
これで大丈夫かと思ったんだけど、テストしてみるとリソースの作成、更新に失敗しているみたい。ActiveRecord::Baseを見てみる。
def to_xml(options={}) attributes.to_xml({:root => self.class.element_name}.merge(options)) end def create returning connection.post(collection_path, to_xml, self.class.headers) do |response| self.id = id_from_response(response) load_attributes_from_response(response) end end
データをXMLで送ってるのかな。ActiveResource::Connection#postを追ってみる。
def post(path, body = '', headers = {}) request(:post, path, body.to_s, build_request_headers(headers)) end def default_header @default_header ||= { 'Content-Type' => format.mime_type } end def build_request_headers(headers) authorization_header.update(default_header).update(headers) end
Content-type: application/json で、 /shops.json に、データをXMLで送ってたみたいだ。受け取ってる側のログをみると、
Processing ShopsController#create (for 192.168.1.9 at 2008-05-29 15:01:15) [POST] Parameters: {"format"=>"json", "action"=>"create", "controller"=>"shops"} Completed in 0.38161 (2 reqs/sec) | Rendering: 0.00029 (0%) | DB: 0.00000 (0%) | 422 Unprocessable Entity [http://taslam-example.jp/shops.json]
やっぱり、データが受け取れてないや。
ひとまず、検索時だけJSONで高速化できれば当初の目的は達成できる(し、JSONに変更したことでもし不具合がでてもデータの更新ができないなどということにならない)ので、findを使う際のみformatを切り替えることにした。
class Shop << ActiveResource::Base self.site = 'http://taslam-example.jp/' def self.find(*args) self.format = self.connection.format = ActiveResource::Formats[:ex_json] super(*args) ensure self.format = self.connection.format = ActiveResource::Formats[:xml] end end