27
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

jbuilderのpartialを高速化する

Last updated at Posted at 2017-10-16

かなり有名な話かと思うが、 jbuilderのpartialは遅い
ActionViewの機能の render partial: '...' を内部的に呼び出しており、それが遅いからである。
そのため、数の多いレコードをjbuilderでレンダリングすると、だいたい数百msかかる。

# 内部でレコードごとにrender呼び出しているのが原因で遅い
json.posts('posts/post', collection: @posts, as: :post)

これを解決するために、 partial! を呼び出さずに直接埋め込むこともある。
しかし、せっかく構造化して切り分けられたテンプレートを埋め込むと、見通しが悪くなり残念である。

json.posts(@posts) do |post|
  json.call(post, ...)
end

というわけで、テンプレートを分割したまま高速化することを考えてみる。
効果の高そうな順に並べてみた。

1. partialのログをオフにする

デフォルトでは、Railsはレンダリングしたテンプレートの情報をログに書き出す。
下記のようなログは、見覚えがあるのではないだろうか?

INFO -- : Rendered api/posts/_post.json.jbuilder (8.0ms)
INFO -- : Rendered api/comments/_comment.json.jbuilder (0.2ms)
INFO -- : Rendered api/comments/_comment.json.jbuilder (0.8ms)
INFO -- : Rendered api/comments/_comment.json.jbuilder (0.9ms)
INFO -- : Rendered api/comments/_comment.json.jbuilder (0.3ms)
INFO -- : Rendered api/comments/_comment.json.jbuilder (1.0ms)
...

通常は軽微なものなのだが、数が多いとパフォーマンスに影響が出る。
なので、jbuilderの場合はログの出力を抑えるのを試みる。

# config/initializers/optimized_jbuilder_partial_rendering.rb
module OptimizeJBuilderPartialRendering
  def render_partial(event)
    # jbuilderの場合はログを書き出さない
    super unless event.payload[:identifier].end_with?('.jbuilder')
  end
end

ActiveSupport.on_load(:action_view) do
  ActionView::LogSubscriber.prepend(OptimizeJBuilderPartialRendering) if Rails.env.production?
end

これで、検証環境では倍ぐらい速くなった。

2. 探索の正規表現を簡素にする

テンプレートの探索は、複数のパス(prefix + view_path)の Dir[Regexp] を回して行われる。
端的に言えば、 Regexpのコスト * 探索回数 がテンプレート探索のコストである。
そのため、Regexpの内容を改良することで、テンプレートの探索速度が速くなる可能性がある。

ちなみにRegexpの中身はこんな感じで、locale、format、handlersなどが考慮されている。

%r!app/views/api/posts/_post{.ja,}{.html,.json,}{}{.erb,.builder,.raw,.ruby,.slim,.coffee,.haml,.faml,.jbuilder,}!

#details_for_lookup を上書きして、自分にとって不要な要素を削ることで高速化を試みる。

class Api::PostsController < Api::ApplicationController
  ...

  private

  # 実はjbuilderはhandlersのみ最適化しているので、handlersはなくても良い。
  # ただし、初回にcontrollerがrenderを呼び出す際には効果がある。
  def details_for_lookup
    {
      locale: [],
      format: [:json],
      handlers: [:jbuilder]
    }
  end
end

巨大なアプリケーションでは、HTMLの探索が10倍ぐらい速くなったことがあるのだが、今回は効果がなかった。
検証環境は小さいアプリケーションなのと、jbuilderの最適化、ActionViewの探索パスのキャッシュが既に効いているからだと思う。

ちなみに、自分のアプリケーションを変更したい時は YourController.new.lookup_context.instance_variable_get(:@details) でRegexpの大体の予想ができる。


今後も検証することがあれば追記していく。

ベンチマークは、APIアプリケーションを作って試しているだけなので、ぜひフィードバックください。 :pray:

27
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?