From 4e85fc9dae04747e748bb393d304a0911143c885 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 11 Nov 2014 16:28:59 +1100 Subject: [PATCH] PERF: cache all site_text in memory --- app/models/site_text.rb | 8 +++++++ lib/site_setting_extension.rb | 1 + lib/site_text_class_methods.rb | 40 ++++++++++++++++++++++++++++++++++ spec/models/site_text_spec.rb | 22 ++++++++++++++++--- 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/app/models/site_text.rb b/app/models/site_text.rb index 425b4299401..03e0dabbdc3 100644 --- a/app/models/site_text.rb +++ b/app/models/site_text.rb @@ -2,11 +2,19 @@ require_dependency 'site_text_type' require_dependency 'site_text_class_methods' class SiteText < ActiveRecord::Base + + # needed for site text class methods + @mutex = Mutex.new + @text_for_cache = {} extend SiteTextClassMethods self.primary_key = 'text_type' validates_presence_of :value + after_save do + MessageBus.publish '/text_for', self.text_type + end + def self.formats @formats ||= Enum.new(:plain, :markdown, :html, :css) end diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index 3bec7f0e674..0fc0ab7eb74 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -289,6 +289,7 @@ module SiteSettingExtension protected def clear_cache! + MessageBus.publish '/text_for', 'site_settings' Rails.cache.delete(SiteSettingExtension.client_settings_cache_key) end diff --git a/lib/site_text_class_methods.rb b/lib/site_text_class_methods.rb index 7e5dafd0daf..5482ad42b35 100644 --- a/lib/site_text_class_methods.rb +++ b/lib/site_text_class_methods.rb @@ -16,6 +16,41 @@ module SiteTextClassMethods end def text_for(text_type, replacements=nil) + text = nil + text = cached_text_for(text_type) if replacements.blank? + text ||= uncached_text_for(text_type, replacements) + end + + def cached_text_for(text_type) + ensure_subscribed! + @mutex.synchronize do + cache = @text_for_cache[RailsMultisite::ConnectionManagement.current_db] + cache[text_type] if cache + end + end + + def store_cached_text_for(text_type, result) + ensure_subscribed! + @mutex.synchronize do + cache = (@text_for_cache[RailsMultisite::ConnectionManagement.current_db] ||= {}) + cache[text_type] = result + end + end + + def ensure_subscribed! + return if @subscribed + @mutex.synchronize do + MessageBus.subscribe("/text_for") do |message| + @mutex.synchronize do + @text_for_cache[message.site_id] = nil + end + end + end + end + + def uncached_text_for(text_type, replacements=nil) + store_cache = replacements.blank? + replacements ||= {} replacements = {site_name: SiteSetting.title}.merge!(replacements) replacements = SiteSetting.settings_hash.merge!(replacements) @@ -34,6 +69,11 @@ module SiteTextClassMethods replacements[m[2..-2].to_sym] || m end + if store_cache + result.freeze + store_cached_text_for(text_type, result) + end + result end diff --git a/spec/models/site_text_spec.rb b/spec/models/site_text_spec.rb index 0ff9ecc3bd7..3823e984726 100644 --- a/spec/models/site_text_spec.rb +++ b/spec/models/site_text_spec.rb @@ -8,13 +8,29 @@ describe SiteText do describe "#text_for" do it "returns an empty string for a missing text_type" do - SiteText.text_for('breaking.bad').should == "" + SiteText.text_for('something_random').should == "" end it "returns the default value for a text` type with a default" do SiteText.text_for("usage_tips").should be_present end + it "correctly expires and bypasses cache" do + SiteSetting.enable_sso = false + text = SiteText.create!(text_type: "got.sso", value: "got sso: %{enable_sso}") + SiteText.text_for("got.sso").should == "got sso: false" + SiteText.text_for("got.sso").frozen? == true + + SiteSetting.enable_sso = true + SiteText.text_for("got.sso").should == "got sso: true" + + text.value = "I gots sso: %{enable_sso}" + text.save! + + SiteText.text_for("got.sso").should == "I gots sso: true" + SiteText.text_for("got.sso", enable_sso: "frog").should == "I gots sso: frog" + end + context "without replacements" do let!(:site_text) { Fabricate(:site_text_basic) } @@ -46,12 +62,12 @@ describe SiteText do let!(:site_text) { Fabricate(:site_text_site_setting) } it "replaces site_settings by default" do - SiteSetting.stubs(:title).returns("Evil Trout") + SiteSetting.title = "Evil Trout" SiteText.text_for('site.replacement').should == "Evil Trout is evil." end it "allows us to override the default site settings" do - SiteSetting.stubs(:title).returns("Evil Trout") + SiteSetting.title = "Evil Trout" SiteText.text_for('site.replacement', title: 'Good Tuna').should == "Good Tuna is evil." end