PERF: introduce site/global emoji cache (#15899)

Previously calls such as `Emoji["smile"]` would force a full dehydration of
objects from Redis.

This introduces a version safe site and global emoji cache so lookups are
cheap. It eliminates iterating through the list of emojis and pulling from
redis.

Distributed cache uses a normalized name as the key and stores an Array tuple
with version and Emoji. Successful hits always confirm version matches.

Interface to Emoji object remains unchanged.

We opted for 2 caches to improve reuse on multisites. misses though will be
stored in both caches. If there is a hit on the global cache we can avoid
looking up in site local cache and storing a miss there.
This commit is contained in:
Sam 2022-02-16 12:46:17 +11:00 committed by GitHub
parent c9419b51a3
commit 33a0ad1b69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 67 additions and 4 deletions

View File

@ -12,6 +12,14 @@ class Emoji
attr_accessor :name, :url, :tonable, :group attr_accessor :name, :url, :tonable, :group
def self.global_emoji_cache
@global_emoji_cache ||= DistributedCache.new("global_emoji_cache", namespace: false)
end
def self.site_emoji_cache
@site_emoji_cache ||= DistributedCache.new("site_emoji_cache")
end
def self.all def self.all
Discourse.cache.fetch(cache_key("all_emojis")) { standard | custom } Discourse.cache.fetch(cache_key("all_emojis")) { standard | custom }
end end
@ -54,10 +62,28 @@ class Emoji
is_toned = name.match?(/.+:t[1-6]/) is_toned = name.match?(/.+:t[1-6]/)
normalized_name = name.gsub(/(.+):t[1-6]/, '\1') normalized_name = name.gsub(/(.+):t[1-6]/, '\1')
Emoji.all.detect do |e| found_emoji = nil
e.name == normalized_name &&
(!is_toned || (is_toned && e.tonable)) [[global_emoji_cache, :standard], [site_emoji_cache, :custom]].each do |cache, list_key|
cache_postfix, found_emoji = cache.defer_get_set(normalized_name) do
emoji = Emoji.public_send(list_key).detect do |e|
e.name == normalized_name &&
(!is_toned || (is_toned && e.tonable))
end
[self.cache_postfix, emoji]
end
if found_emoji && (cache_postfix != self.cache_postfix)
cache.delete(normalized_name)
redo
end
if found_emoji
break
end
end end
found_emoji
end end
def self.create_from_db_item(emoji) def self.create_from_db_item(emoji)
@ -81,13 +107,19 @@ class Emoji
end end
def self.cache_key(name) def self.cache_key(name)
"#{name}:v#{EMOJI_VERSION}:#{Plugin::CustomEmoji.cache_key}" "#{name}#{cache_postfix}"
end
def self.cache_postfix
":v#{EMOJI_VERSION}:#{Plugin::CustomEmoji.cache_key}"
end end
def self.clear_cache def self.clear_cache
%w{custom standard aliases search_aliases translations all tonable}.each do |key| %w{custom standard aliases search_aliases translations all tonable}.each do |key|
Discourse.cache.delete(cache_key("#{key}_emojis")) Discourse.cache.delete(cache_key("#{key}_emojis"))
end end
global_emoji_cache.clear
site_emoji_cache.clear
end end
def self.db_file def self.db_file

View File

@ -87,6 +87,37 @@ describe Emoji do
end end
end end
describe 'version updates' do
it 'should correct cache when global emojis cache is stale' do
Emoji.global_emoji_cache["blonde_man"] = [
"invalid",
Emoji.new
]
emoji = Emoji[":blonde_man:t3"]
expect(emoji.name).to eq('blonde_man')
expect(emoji.tonable).to eq(true)
end
it 'should correct cache when site emojis cache is stale' do
CustomEmoji.create!(name: 'test123', upload_id: 9999)
Emoji.clear_cache
Emoji.site_emoji_cache["test123"] = [
"invalid",
Emoji.new
]
emoji = Emoji[":test123:"]
expect(emoji.name).to eq('test123')
expect(emoji.tonable).to be_falsey
Emoji.clear_cache
end
end
describe '.codes_to_img' do describe '.codes_to_img' do
before { Plugin::CustomEmoji.clear_cache } before { Plugin::CustomEmoji.clear_cache }
after { Plugin::CustomEmoji.clear_cache } after { Plugin::CustomEmoji.clear_cache }