2013-05-25 12 views
7

Ostatnio przełączyłem się z domyślnego prostego backendu I18n na backend Redis dla mojego I18n. Zrobiłem to, więc ułatwiło nam obsługę tłumaczeń, ale zauważyłem, że na każdej stronie było znaczące uderzenie wydajności.Czy Redis jest zbyt wolny dla produkcji Railsów I18n?

Uruchomiłem niektóre testy porównawcze z Rails 3.2 i Redis 2.6.4 zainstalowanymi na moim MBP w celu zademonstrowania. Używam hiredis-rb jako mojego klienta.

Jest to dość wyraźna różnica podczas korzystania z dwóch różnych elementów zaplecza. Z prostego backend jest krótka przerwa na pierwsze wezwanie - Zakładam, że tłumaczenia są ładowane do pamięci - a potem wielkiej wydajności po tym:

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 backend jest konsekwentnie powolna:

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 

To ma dla mnie absolutny sens, dlaczego jest to powolne dla I18n ... Czekam na dziesiątki połączeń I18n w całej mojej bazie kodu. Gdybym mógł partia je razem z góry byłbym w dobrej kondycji:

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

Ale ja nie widzę czystą sposób to zrobić z żadnym z istniejących backendów i18n. Czy ktoś tam rozwiązał ten problem?

EDIT

Wziąłem sugestię Chris Heald i stworzył backend z memoization prosty cache biust. Istotą jest tutaj:

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

Postaram się to na kilka dni, a następnie przekształcić go w gem.

UPDATE

Moje rozwiązanie jest dostępne jako klejnot teraz:

https://github.com/wheeyls/cached_key_value_store

I również napisał o tym problemie:

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

+0

Gdzie jest twój serwer redis w odniesieniu do skrzynki z tymi wzorcami? – deefour

+0

Te zostały uruchomione na mojej lokalnej maszynie. Zmienię moje pytanie, by wyjaśnić. – Wheeyls

Odpowiedz

3

ruchu sieciowego zawsze będzie wolniejszy niż praca lokalna. Możesz rozważyć pamięć podręczną w pamięci, a następnie po prostu pobrać bieżącą wersję lokalizacji dla każdego żądania (lub nawet tylko na krótkim czasomierzu), aby określić, czy unieważnić pamięć podręczną. Wygląda na to, że istnieje moduł Memoization (na the source here), który można po prostu miksować w interfejsie I18n. Następnie po prostu dostosowujemy metodę #lookup tak, aby co 5 minut sprawdzała Redis pod kątem zaktualizowanej wersji językowej i upewnia się, że zwiększa ona wersję ustawień regionalnych po zapisaniu nowych tłumaczeń.

Daje to pamięć podręczną w pamięci wszystkich tłumaczeń, więc jest to bardzo szybkie wyszukiwanie, umożliwiając jednocześnie wprowadzanie zmian w czasie tłumaczenia - aktualizacja może potrwać do 5 minut, ale nie musisz wykonywać wyraźnego czyszczenia pamięci podręcznej.

Jeśli chcesz, możesz sprawić, by sprawdzał każde żądanie z numerem before_filter, zamiast używać tylko leniwe 5-minutowe wygaśnięcie, co oznacza więcej próśb o ponowne wysłanie, ale nie zobaczysz żadnych nieaktualnych tłumaczeń.

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 

właśnie włamał się od czytania tego źródła I18n, a ja nie testowałem go w ogóle, więc to może trzeba popracować, ale myślę, że komunikuje się pomysł dość dobrze.

+0

Nifty. Jest to w zasadzie rozwiązanie, które miałem na myśli, tylko lepsze. Zastanawiam się, dlaczego nikt inny nie wydaje się, żeby sobie z tym poradzić? – Wheeyls

+0

Gist jest tutaj: https://gist.github.com/wheeyls/5650947 – Wheeyls

+0

Nice. Zamierzam to zilustrować i wsadzić do mojego stosu na później. :) –