Commit d4e92699 authored by Kerri Miller's avatar Kerri Miller

Migrate HighlightCache -> DeprecatedHighlightCache

Originally I had hoped to feature-flag the changes to support redis hash
caching, as they seemed small, but of course those changes expanded and
expanded until it became clear it really needed to be extracted into a
new class with a parallel API to keep it clean. h/t to @oswaldo for
pointing out when we'd crossed that threshold!
parent cc1442bb
# frozen_string_literal: true
#
module Gitlab
module Diff
class DeprecatedHighlightCache
delegate :diffable, to: :@diff_collection
delegate :diff_options, to: :@diff_collection
def initialize(diff_collection, backend: Rails.cache)
@backend = backend
@diff_collection = diff_collection
end
# - Reads from cache
# - Assigns DiffFile#highlighted_diff_lines for cached files
def decorate(diff_file)
if content = read_file(diff_file)
diff_file.highlighted_diff_lines = content.map do |line|
Gitlab::Diff::Line.init_from_hash(line)
end
end
end
# It populates a Hash in order to submit a single write to the memory
# cache. This avoids excessive IO generated by N+1's (1 writing for
# each highlighted line or file).
def write_if_empty
return if cached_content.present?
@diff_collection.diff_files.each do |diff_file|
next unless cacheable?(diff_file)
diff_file_id = diff_file.file_identifier
cached_content[diff_file_id] = diff_file.highlighted_diff_lines.map(&:to_hash)
end
cache.write(key, cached_content, expires_in: 1.week)
end
def clear
cache.delete(key)
end
def key
[diffable, 'highlighted-diff-files', Gitlab::Diff::Line::SERIALIZE_KEYS, diff_options]
end
private
def read_file(diff_file)
cached_content[diff_file.file_identifier]
end
def cache
@backend
end
def cached_content
@cached_content ||= cache.read(key) || {}
end
def cacheable?(diff_file)
diffable.present? && diff_file.text? && diff_file.diffable?
end
end
end
end
...@@ -33,7 +33,8 @@ module Gitlab ...@@ -33,7 +33,8 @@ module Gitlab
private private
def cache def cache
@cache ||= Gitlab::Diff::HighlightCache.new(self) # if Feature.enabled?(:redis_diff_caching)
@cache ||= Gitlab::Diff::DeprecatedHighlightCache.new(self)
end end
end end
end end
......
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
def initialize(diff_collection, backend: Rails.cache) def initialize(diff_collection, backend: Rails.cache)
@backend = backend @backend = backend
@diff_collection = diff_collection @diff_collection = diff_collection
@redis_key = "highlighted-diff-files:#{diffable.cache_key}" if Feature.enabled?(:redis_diff_caching) @redis_key = "highlighted-diff-files:#{diffable.cache_key}"
end end
# - Reads from cache # - Reads from cache
...@@ -38,11 +38,7 @@ module Gitlab ...@@ -38,11 +38,7 @@ module Gitlab
cached_content[diff_file_id] = diff_file.highlighted_diff_lines.map(&:to_hash) cached_content[diff_file_id] = diff_file.highlighted_diff_lines.map(&:to_hash)
end end
if Feature.enabled?(:redis_diff_caching) write_to_redis_hash(cached_content)
write_to_redis_hash(cached_content)
else
cache.write(key, cached_content, expires_in: 1.week)
end
end end
# Given a hash of: # Given a hash of:
...@@ -85,11 +81,9 @@ module Gitlab ...@@ -85,11 +81,9 @@ module Gitlab
end end
def clear def clear
cache.delete(key) Redis::Cache.with do |redis|
end redis.del(@redis_key)
end
def key
[diffable, 'highlighted-diff-files', Gitlab::Diff::Line::SERIALIZE_KEYS, diff_options]
end end
private private
...@@ -98,20 +92,12 @@ module Gitlab ...@@ -98,20 +92,12 @@ module Gitlab
cached_content[diff_file.file_identifier] cached_content[diff_file.file_identifier]
end end
def cache
@backend
end
def cached_content def cached_content
@cached_content ||= populate_cached_content @cached_content ||= populate_cached_content
end end
def populate_cached_content def populate_cached_content
if Feature.enabled?(:redis_diff_caching) read_entire_redis_hash.transform_values! { |v| v.present? ? JSON.parse(v, symbolize_names: true) : nil }
read_entire_redis_hash.transform_values! { |v| v.present? ? JSON.parse(v, symbolize_names: true) : nil }
else
cache.read(key) || {}
end
end end
def cacheable?(diff_file) def cacheable?(diff_file)
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Diff::DeprecatedHighlightCache do
let(:merge_request) { create(:merge_request_with_diffs) }
subject(:cache) { described_class.new(merge_request.diffs, backend: backend) }
describe '#decorate' do
let(:backend) { double('backend').as_null_object }
# Manually creates a Diff::File object to avoid triggering the cache on
# the FileCollection::MergeRequestDiff
let(:diff_file) do
diffs = merge_request.diffs
raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['CHANGELOG'])).first
Gitlab::Diff::File.new(raw_diff,
repository: diffs.project.repository,
diff_refs: diffs.diff_refs,
fallback_diff_refs: diffs.fallback_diff_refs)
end
it 'does not calculate highlighting when reading from cache' do
cache.write_if_empty
cache.decorate(diff_file)
expect_any_instance_of(Gitlab::Diff::Highlight).not_to receive(:highlight)
diff_file.highlighted_diff_lines
end
it 'assigns highlighted diff lines to the DiffFile' do
cache.write_if_empty
cache.decorate(diff_file)
expect(diff_file.highlighted_diff_lines.size).to be > 5
end
it 'submits a single reading from the cache' do
cache.decorate(diff_file)
cache.decorate(diff_file)
expect(backend).to have_received(:read).with(cache.key).once
end
end
describe '#write_if_empty' do
let(:backend) { double('backend', read: {}).as_null_object }
it 'submits a single writing to the cache' do
cache.write_if_empty
cache.write_if_empty
expect(backend).to have_received(:write).with(cache.key,
hash_including('CHANGELOG-false-false-false'),
expires_in: 1.week).once
end
end
describe '#clear' do
let(:backend) { double('backend').as_null_object }
it 'clears cache' do
cache.clear
expect(backend).to have_received(:delete).with(cache.key)
end
end
end
...@@ -37,6 +37,8 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do ...@@ -37,6 +37,8 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
new_pos: 20 }] } new_pos: 20 }] }
end end
let(:cache_key) { cache.instance_variable_get(:@redis_key) }
subject(:cache) { described_class.new(merge_request.diffs, backend: backend) } subject(:cache) { described_class.new(merge_request.diffs, backend: backend) }
describe '#decorate' do describe '#decorate' do
...@@ -68,56 +70,22 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do ...@@ -68,56 +70,22 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
expect(diff_file.highlighted_diff_lines.size).to be > 5 expect(diff_file.highlighted_diff_lines.size).to be > 5
end end
context 'when :redis_diff_caching is not enabled' do
before do
expect(Feature).to receive(:enabled?).with(:redis_diff_caching).and_return(false)
end
it 'submits a single reading from the cache' do
expect(Feature).to receive(:enabled?).with(:redis_diff_caching).at_least(:once).and_return(false)
2.times { cache.decorate(diff_file) }
expect(backend).to have_received(:read).with(cache.key).once
end
end
end end
describe '#write_if_empty' do describe '#write_if_empty' do
let(:backend) { double('backend', read: {}).as_null_object } let(:backend) { double('backend', read: {}).as_null_object }
context 'when :redis_diff_caching is enabled' do context 'when :redis_diff_caching is enabled' do
before do
expect(Feature).to receive(:enabled?).with(:redis_diff_caching).and_return(true)
end
it 'submits a single write action to the redis cache when invoked multiple times' do it 'submits a single write action to the redis cache when invoked multiple times' do
expect(cache).to receive(:write_to_redis_hash).once expect(cache).to receive(:write_to_redis_hash).once
2.times { cache.write_if_empty } 2.times { cache.write_if_empty }
end end
end end
context 'when :redis_diff_caching is not enabled' do
before do
expect(Feature).to receive(:enabled?).with(:redis_diff_caching).at_least(:once).and_return(false)
end
it 'submits a single writing to the cache' do
2.times { cache.write_if_empty }
expect(backend)
.to have_received(:write)
.with(cache.key, hash_including('CHANGELOG-false-false-false'), expires_in: 1.week)
.once
end
end
end end
describe '#write_to_redis_hash' do describe '#write_to_redis_hash' do
let(:backend) { Rails.cache } let(:backend) { Rails.cache }
let(:cache_key) { cache.diffable.cache_key }
it 'creates or updates a Redis hash' do it 'creates or updates a Redis hash' do
expect { cache.write_to_redis_hash(diff_hash) } expect { cache.write_to_redis_hash(diff_hash) }
...@@ -127,7 +95,6 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do ...@@ -127,7 +95,6 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
describe '#read_entire_redis_hash' do describe '#read_entire_redis_hash' do
let(:backend) { Rails.cache } let(:backend) { Rails.cache }
let(:cache_key) { cache.diffable.cache_key }
before do before do
cache.write_to_redis_hash(diff_hash) cache.write_to_redis_hash(diff_hash)
...@@ -142,7 +109,6 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do ...@@ -142,7 +109,6 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
describe '#read_single_entry_from_redis_hash' do describe '#read_single_entry_from_redis_hash' do
let(:backend) { Rails.cache } let(:backend) { Rails.cache }
let(:cache_key) { cache.diffable.cache_key }
before do before do
cache.write_to_redis_hash(diff_hash) cache.write_to_redis_hash(diff_hash)
...@@ -161,9 +127,9 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do ...@@ -161,9 +127,9 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
let(:backend) { double('backend').as_null_object } let(:backend) { double('backend').as_null_object }
it 'clears cache' do it 'clears cache' do
cache.clear expect_any_instance_of(Redis).to receive(:del).with(cache_key)
expect(backend).to have_received(:delete).with(cache.key) cache.clear
end end
end end
end end
...@@ -88,7 +88,7 @@ describe Notes::CreateService do ...@@ -88,7 +88,7 @@ describe Notes::CreateService do
end end
it 'clears noteable diff cache when it was unfolded for the note position' do it 'clears noteable diff cache when it was unfolded for the note position' do
expect_any_instance_of(Gitlab::Diff::HighlightCache).to receive(:clear) expect_any_instance_of(Gitlab::Diff::DeprecatedHighlightCache).to receive(:clear)
described_class.new(project_with_repo, user, new_opts).execute described_class.new(project_with_repo, user, new_opts).execute
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment