airbnb/hypernova-ruby
を普通に使ってfragment cacheをしようと思うと事故るのでその話を。
ちなみに現在の最新版v1.3.0での話です。
なぜ事故るのか
hypernova-rubyの使い方
contollerにaround_filterを追加して
class MyController < ApplicationController
around_filter :hypernova_render_support
end
テンプレート内で
<%= render_react_component('MyComponent', props) %>
と使います。
実際何が起きているのか
render_react_component
はSSRされたものと置換される文字列(トークン)を返す。トークンとコンポーネント名、propsの対応関係を持っておく。- テンプレートのrender完了後に全てのコンポーネントのぶんをまとめてhypernova/serverにbatchリクエストを送る。
- 1からの対応関係に応じて、
response.body
内のトークンをSSRしたものに置換する。
という処理が行われます。
なので
<% cache 'MyComponent' do %>
<%= render_react_component('MyComponent', props) %>
<% end %>
等としてしまうと置換前のトークンがキャッシュされて、キャッシュされる初回のみは正しく表示されるものの、
それ移行のリクエストではそのトークンに対応するものがbatchリクエストに含まれないので、トークンが置換されずにそのまま表示されるということが起きてしまいます。
どうするか
hypernova-rubyのREADMEにも書いてある低レベルAPIを使って、ブロックごとにhypernova/serverへのリクエストと置換までやってしまうhelperを書きます。
module ApplicationHelper
def hypernova_batch_replace(&block)
builder = HypernovaBatchBuilder.new(self, @_hypernova_service)
str = capture(builder, &block)
builder.replace(str)
end
end
ここでHypernovaBatchBuilder
は以下。
class HypernovaBatchBuilder
def initialize(service)
@batch = Hypernova::Batch.new(service)
@token_uuid_map = {}
end
def render_react_component(name, **props)
uuid = SecureRandom.uuid
token = @batch.render(name: name, data: props)
@token_uuid_map[token] = uuid
uuid
end
def replace(str)
result = @batch.submit!
result.reduce(str) do |s, (token, v)|
uuid = @token_uuid_map[token]
s.sub(uuid, v)
end.html_safe
end
end
これをテンプレート内で
<% cache 'cache-key' do %>
<%= hypernova_batch_replace do |b| %>
<%= b.render_react_component('MyComponent', props) %>
<% end %>
<% end %>
と使います。
HypernovaBatchBuilder
に色々生やせば自分のユースケースにあったことがもっと色々出来ると思います。
例えば、クライアント側でrenderしない場合にはnokogiri等でdata関係の余計な要素を削るようなメソッドを生やすとか。
注意点としては、
- 上の例ではhypernovaサーバへのリクエストが失敗した場合のfallbackがないので、例えばhypernovaサーバが立ちあがっていないと例外を吐く
- キャッシュにのっていない際のリクエストで
hypernova_batch_replace
を使うと、hypernova/serverへのリクエスト数が増えるので、そのリクエストに関してはもちろん遅くなる hypernova_render_support
でもbatchリクエストで処理されているので、多くのリクエストで全てのコンポーネントがキャッシュから読み込まれる(1リクエスト内でのhypernova/serverへの平均リクエスト数が1を切る)ようでないと旨味は無い- hypernova/serverはまぁまぁ速い(自分が使ってた環境では平均30~40ms)ので、キャッシュの読み書きが増える分、全体として速くなるかどうかはキャッシュストアやコンポーネント数次第
あたりですかね。
自分の場合には平均レスポンス時間で~30msぐらいは削れました。