Commit cc33149a authored by Rémy Coutable's avatar Rémy Coutable

Merge branch '228626-reduce-cached-queries-for-banzai-filters' into 'master'

Reduce  'cached' calls for Banzai

Closes #228626

See merge request gitlab-org/gitlab!36735
parents 49cfd3aa a241581c
---
title: Reduce 'cached' query calls for Banzai
merge_request: 36735
author:
type: performance
...@@ -19,7 +19,7 @@ module Banzai ...@@ -19,7 +19,7 @@ module Banzai
unescaped_html = unescape_html_entities(text).gsub(pattern) do |match| unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
namespace, project = $~[:namespace], $~[:project] namespace, project = $~[:namespace], $~[:project]
project_path = full_project_path(namespace, project) project_path = full_project_path(namespace, project)
label = find_label(project_path, $~[:label_id], $~[:label_name]) label = find_label_cached(project_path, $~[:label_id], $~[:label_name])
if label if label
labels[label.id] = yield match, label.id, project, namespace, $~ labels[label.id] = yield match, label.id, project, namespace, $~
...@@ -34,6 +34,12 @@ module Banzai ...@@ -34,6 +34,12 @@ module Banzai
escape_with_placeholders(unescaped_html, labels) escape_with_placeholders(unescaped_html, labels)
end end
def find_label_cached(parent_ref, label_id, label_name)
cached_call(:banzai_find_label_cached, label_name&.tr('"', '') || label_id, path: [object_class, parent_ref]) do
find_label(parent_ref, label_id, label_name)
end
end
def find_label(parent_ref, label_id, label_name) def find_label(parent_ref, label_id, label_name)
parent = parent_from_ref(parent_ref) parent = parent_from_ref(parent_ref)
return unless parent return unless parent
......
...@@ -169,7 +169,8 @@ module Banzai ...@@ -169,7 +169,8 @@ module Banzai
# been queried the object is returned from the cache. # been queried the object is returned from the cache.
def collection_objects_for_ids(collection, ids) def collection_objects_for_ids(collection, ids)
if Gitlab::SafeRequestStore.active? if Gitlab::SafeRequestStore.active?
ids = ids.map(&:to_i) ids = ids.map(&:to_i).uniq
cache = collection_cache[collection_cache_key(collection)] cache = collection_cache[collection_cache_key(collection)]
to_query = ids - cache.keys to_query = ids - cache.keys
......
...@@ -9,10 +9,23 @@ module Banzai ...@@ -9,10 +9,23 @@ module Banzai
Snippet Snippet
end end
# Returns all the nodes that are visible to the given user.
def nodes_visible_to_user(user, nodes)
snippets = lazy { grouped_objects_for_nodes(nodes, references_relation, self.class.data_attribute) }
nodes.select do |node|
if node.has_attribute?(self.class.data_attribute)
can_read_reference?(user, snippets[node])
else
true
end
end
end
private private
def can_read_reference?(user, ref_project, node) def can_read_reference?(user, snippet)
can?(user, :read_snippet, referenced_by([node]).first) can?(user, :read_snippet, snippet)
end end
end end
end end
......
...@@ -31,6 +31,19 @@ RSpec.describe Banzai::Filter::LabelReferenceFilter do ...@@ -31,6 +31,19 @@ RSpec.describe Banzai::Filter::LabelReferenceFilter do
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label has-tooltip gl-link gl-label-link' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label has-tooltip gl-link gl-label-link'
end end
it 'avoids N+1 cached queries', :use_sql_query_cache, :request_store do
# Run this once to establish a baseline
reference_filter("Label #{reference}")
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
reference_filter("Label #{reference}")
end
labels_markdown = Array.new(10, "Label #{reference}").join('\n')
expect { reference_filter(labels_markdown) }.not_to exceed_all_query_limit(control_count.count)
end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
doc = reference_filter("Label #{reference}") doc = reference_filter("Label #{reference}")
link = doc.css('a').first link = doc.css('a').first
......
...@@ -33,6 +33,17 @@ RSpec.describe Banzai::ReferenceParser::SnippetParser do ...@@ -33,6 +33,17 @@ RSpec.describe Banzai::ReferenceParser::SnippetParser do
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::ENABLED) project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::ENABLED)
end end
it 'avoids N+1 cached queries', :use_sql_query_cache do
# Run this once to establish a baseline
visible_references(:public)
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
subject.nodes_visible_to_user(user, [link])
end
expect { subject.nodes_visible_to_user(user, Array.new(10, link)) }.not_to exceed_all_query_limit(control_count.count)
end
it 'creates a reference for guest for a public snippet' do it 'creates a reference for guest for a public snippet' do
expect(visible_references(:public)).to eq([link]) expect(visible_references(:public)).to eq([link])
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