Browse Source

Add entity cache (#7271)

* Add entity cache

Use a caching layer for mentions and custom emojis that are
dynamically extracted from text.

Reduce duplicate text extractions

* Fix code style issue
Eugen Rochko 6 years ago
parent
commit
a872392cd9
5 changed files with 48 additions and 10 deletions
  1. 34 0
      app/lib/entity_cache.rb
  2. 3 7
      app/lib/formatter.rb
  3. 1 1
      app/models/account.rb
  4. 9 1
      app/models/custom_emoji.rb
  5. 1 1
      app/models/status.rb

+ 34 - 0
app/lib/entity_cache.rb

@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'singleton'
+
+class EntityCache
+  include Singleton
+
+  MAX_EXPIRATION = 7.days.freeze
+
+  def mention(username, domain)
+    Rails.cache.fetch(to_key(:mention, username, domain), expires_in: MAX_EXPIRATION) { Account.select(:username, :domain, :url).find_remote(username, domain) }
+  end
+
+  def emoji(shortcodes, domain)
+    shortcodes   = [shortcodes] unless shortcodes.is_a?(Array)
+    cached       = Rails.cache.read_multi(*shortcodes.map { |shortcode| to_key(:emoji, shortcode, domain) })
+    uncached_ids = []
+
+    shortcodes.each do |shortcode|
+      uncached_ids << shortcode unless cached.key?(to_key(:emoji, shortcode, domain))
+    end
+
+    unless uncached_ids.empty?
+      uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).select(:shortcode, :id, :image_file_name, :visible_in_picker).map { |item| [item.shortcode, item] }.to_h
+      uncached.each_value { |item| Rails.cache.write(to_key(:emoji, item.shortcode, domain), item, expires_in: MAX_EXPIRATION) }
+    end
+
+    shortcodes.map { |shortcode| cached[to_key(:emoji, shortcode, domain)] || uncached[shortcode] }.compact
+  end
+
+  def to_key(type, *ids)
+    "#{type}:#{ids.compact.map(&:downcase).join(':')}"
+  end
+end

+ 3 - 7
app/lib/formatter.rb

@@ -52,12 +52,8 @@ class Formatter
   end
 
   def simplified_format(account, **options)
-    html = if account.local?
-             linkify(account.note)
-           else
-             reformat(account.note)
-           end
-    html = encode_custom_emojis(html, CustomEmoji.from_text(account.note, account.domain)) if options[:custom_emojify]
+    html = account.local? ? linkify(account.note) : reformat(account.note)
+    html = encode_custom_emojis(html, account.emojis) if options[:custom_emojify]
     html.html_safe # rubocop:disable Rails/OutputSafety
   end
 
@@ -211,7 +207,7 @@ class Formatter
     username, domain = acct.split('@')
 
     domain  = nil if TagManager.instance.local_domain?(domain)
-    account = Account.find_remote(username, domain)
+    account = EntityCache.instance.mention(username, domain)
 
     account ? mention_html(account) : "@#{acct}"
   end

+ 1 - 1
app/models/account.rb

@@ -391,7 +391,7 @@ class Account < ApplicationRecord
   end
 
   def emojis
-    CustomEmoji.from_text(note, domain)
+    @emojis ||= CustomEmoji.from_text(note, domain)
   end
 
   before_create :generate_keys

+ 9 - 1
app/models/custom_emoji.rb

@@ -42,6 +42,8 @@ class CustomEmoji < ApplicationRecord
 
   include Attachmentable
 
+  after_commit :remove_entity_cache
+
   def local?
     domain.nil?
   end
@@ -58,11 +60,17 @@ class CustomEmoji < ApplicationRecord
 
       return [] if shortcodes.empty?
 
-      where(shortcode: shortcodes, domain: domain, disabled: false)
+      EntityCache.instance.emoji(shortcodes, domain)
     end
 
     def search(shortcode)
       where('"custom_emojis"."shortcode" ILIKE ?', "%#{shortcode}%")
     end
   end
+
+  private
+
+  def remove_entity_cache
+    Rails.cache.delete(EntityCache.instance.to_key(:emoji, shortcode, domain))
+  end
 end

+ 1 - 1
app/models/status.rb

@@ -160,7 +160,7 @@ class Status < ApplicationRecord
   end
 
   def emojis
-    CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain)
+    @emojis ||= CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain)
   end
 
   after_create_commit :store_uri, if: :local?