11

最近、I18n のデフォルトの Simple I18n バックエンドから Redis バックエンドに切り替えました。翻訳を処理しやすくするためにそうしましたが、すべてのページでかなりのパフォーマンス ヒットがあったことがわかりました。

Rails 3.2 と Redis 2.6.4 を MBP にインストールしていくつかのベンチマークを実行し、デモを行いました。クライアントとしてhiredis-rbを使用しています。

2 つの異なるバックエンドを実行すると、その違いは明らかです。単純なバックエンドでは、最初の呼び出しに短い遅延があります - 翻訳がメモリにロードされていると仮定します - その後、優れたパフォーマンスが得られます:

pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.143246
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.00415
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.004153
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.004056

Redis バックエンドは一貫して遅い:

pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.122448
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.263564
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.232637
pry(main)> Benchmark.realtime { 500.times { I18n.t 'shared.slogan' } }
=> 0.122304

なぜこれが I18n で遅いのか、私には完全に理にかなっています...コードベース全体で何十もの I18n 呼び出しをキューに入れています。それらを前もってバッチ処理できれば、私は良い状態になるでしょう:

pry(main)> keys = $redis.keys[0..500]
pry(main)> Benchmark.realtime { $redis.mget keys }
=> 0.04264

しかし、既存の I18n バックエンドのいずれかでこれを行うクリーンな方法は実際には見当たりません。この問題に取り組んだ人はいますか?

編集

私は Chris Heald の提案を受けて、単純なキャッシュ バストをメモ化するバックエンドを作成しました。要点はここにあります:

https://gist.github.com/wheeyls/5650947

これを数日間試してから、宝石に変えます。

アップデート

私のソリューションは現在gemとして利用できます:

https://github.com/wheeyls/cached_key_value_store

そして、私はこの問題についてブログにも書きました:

http://about.g2crowd.com/faster-i18nredis-on-rails/

4

1 に答える 1

5

ネットワーク トラフィックは、常にローカル作業より遅くなります。メモリ内キャッシュを検討し、リクエストごとに (または短いタイマーでさえも) 現在のローカライズ バージョンを取得して、キャッシュを無効にするかどうかを判断することができます。I18nインターフェースに混ぜるだけのメモ化モジュール(ソースはこちら)があるようです。次に、#lookupメソッドを微調整して、5 分ごとに Redis で更新されたロケール バージョンをチェックし、新しい翻訳が保存されたときにロケール バージョンをインクリメントするようにします。

これにより、すべての翻訳のメモリ内キャッシュが提供されるため、検索が非常に高速になり、翻訳の変更をその場で行うことができます。翻訳の更新には最大 5 分かかる場合がありますが、明示的なキャッシュパージを行う必要があります。

必要に応じて、レイジーな 5 分間の有効期限を使用するだけでなく、すべてのリクエストでチェックを行うようにすることもできますbefore_filter。これは、redis へのより多くのリクエストを意味しますが、古い翻訳は表示されません。

module I18n
  module Backend
    class CachedKeyValueStore < KeyValue
      include Memoize

      def store_translations(locale, data, options = {})
        @store.incr "locale_version:#{locale}"
        reset_memoizations!(locale)
        super
      end

      def lookup(locale, key, scope = nil, options = {})
        ensure_freshness(locale)
        flat_key  = I18n::Backend::Flatten.normalize_flat_keys(locale,
          key, scope, options[:separator]).to_sym
        flat_hash = memoized_lookup[locale.to_sym]
        flat_hash.key?(flat_key) ? flat_hash[flat_key] : (flat_hash[flat_key] = super)
      end

      def ensure_freshness(locale)
        @last_check ||= 0

        if @last_check < 5.minutes.ago
          @last_check = Time.now
          current_version = @store.get "locale_version:#{locale}"
          if @last_version != current_version
            reset_memoizations! locale
            @last_version = current_version
          end
        end
      end
    end
  end
end

I18n ソースを読んでこれをハッキングしただけで、まったくテストしていないので、多少の作業が必要かもしれませんが、アイデアを十分に伝えていると思います。

于 2013-05-25T05:07:25.857 に答える