Redis太慢了Rails生产I18n吗?

时间:2013-05-25 03:24:45

标签: ruby-on-rails internationalization redis

我最近从默认的Simple I18n后端切换到我的I18n的Redis后端。我这样做是为了让我们更容易处理翻译,但我发现每个页面都有很大的性能影响。

我已经在我的MBP上安装了Rails 3.2和Redis 2.6.4来运行一些基准测试来演示。我正在使用hiredis-rb作为我的客户。

在锻炼两个不同的后端时,这是一个非常明显的区别。使用简单的后端,第一次调用会有一个短暂的延迟 - 我假设翻译被加载到内存中 - 然后是很好的性能:

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的建议并创建了一个后备文件,其中memoization是一个简单的缓存区。要点在这里:

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

我会试试这几天然后把它变成一颗宝石。

更新

我的解决方案现已成为宝石:

https://github.com/wheeyls/cached_key_value_store

我还在博客上写了这个问题:

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

1 个答案:

答案 0 :(得分:5)

网络流量总是比本地工作慢。您可以考虑使用内存缓存,然后在每个请求(或者甚至只是在短计时器上)上拉当前的本地化版本,以确定是否使缓存无效。它看起来像一个Memoization模块(每the source here),你可以混合到一个I18n接口。然后,我们只调整#lookup方法,以便每隔5分钟检查一次更新的语言环境版本,并确保在保存新翻译时增加语言环境版本。

这为您提供了所有翻译的内存缓存,因此它可以快速查找,同时让您能够即时更改翻译 - 您的翻译最多可能需要5分钟才能完成,但您不必进行任何显式的缓存清除。

如果您愿意,可以使用before_filter对每个请求进行检查,而不是仅使用懒惰的5分钟到期,这意味着更多的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源码中解决了这个问题,而我根本没有测试过它,所以它可能需要一些工作,但我认为它能很好地传达这个想法。