PERF: Strict loading for SidebarSection queries (#21717)
What is this change required? I noticed that actions in `SidebarSectionsController` resulted in lots of N+1 queries problem and I wanted a solution to prevent such problems without having to write N+1 queries tests. I have also used strict loading for `SidebarSection` queries in performance sensitive spots. Note that in this commit, I have also set `config.active_record.action_on_strict_loading_violation = :log` for the production environment so that we have more visibility of potential N+1 queries problem in the logs. In development and test environment, we're sticking with the default of raising an error.
This commit is contained in:
parent
916495e0a1
commit
5cfe323445
|
@ -359,11 +359,12 @@ export default Controller.extend(ModalFunctionality, {
|
||||||
return ajax(`/sidebar_sections/${this.model.id}`, {
|
return ajax(`/sidebar_sections/${this.model.id}`, {
|
||||||
type: "DELETE",
|
type: "DELETE",
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(() => {
|
||||||
const newSidebarSections =
|
const newSidebarSections =
|
||||||
this.currentUser.sidebar_sections.filter((section) => {
|
this.currentUser.sidebar_sections.filter((section) => {
|
||||||
return section.id !== data["sidebar_section"].id;
|
return section.id !== this.model.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.currentUser.set("sidebar_sections", newSidebarSections);
|
this.currentUser.set("sidebar_sections", newSidebarSections);
|
||||||
this.send("closeModal");
|
this.send("closeModal");
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,9 +7,12 @@ class SidebarSectionsController < ApplicationController
|
||||||
def index
|
def index
|
||||||
sections =
|
sections =
|
||||||
SidebarSection
|
SidebarSection
|
||||||
|
.strict_loading
|
||||||
|
.includes(:sidebar_urls)
|
||||||
.where("public OR user_id = ?", current_user.id)
|
.where("public OR user_id = ?", current_user.id)
|
||||||
.order("(public IS TRUE) DESC")
|
.order("(public IS TRUE) DESC")
|
||||||
.map { |section| SidebarSectionSerializer.new(section, root: false) }
|
.map { |section| SidebarSectionSerializer.new(section, root: false) }
|
||||||
|
|
||||||
render json: sections
|
render json: sections
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -23,7 +26,7 @@ class SidebarSectionsController < ApplicationController
|
||||||
Site.clear_anon_cache!
|
Site.clear_anon_cache!
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: SidebarSectionSerializer.new(sidebar_section)
|
render_serialized(sidebar_section, SidebarSectionSerializer)
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
render_json_error(e.record.errors.full_messages.first)
|
render_json_error(e.record.errors.full_messages.first)
|
||||||
end
|
end
|
||||||
|
@ -35,6 +38,7 @@ class SidebarSectionsController < ApplicationController
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
sidebar_section.update!(section_params.merge(sidebar_urls_attributes: links_params))
|
sidebar_section.update!(section_params.merge(sidebar_urls_attributes: links_params))
|
||||||
sidebar_section.sidebar_section_links.update_all(user_id: sidebar_section.user_id)
|
sidebar_section.sidebar_section_links.update_all(user_id: sidebar_section.user_id)
|
||||||
|
|
||||||
order =
|
order =
|
||||||
sidebar_section
|
sidebar_section
|
||||||
.sidebar_urls
|
.sidebar_urls
|
||||||
|
@ -55,7 +59,7 @@ class SidebarSectionsController < ApplicationController
|
||||||
Site.clear_anon_cache!
|
Site.clear_anon_cache!
|
||||||
end
|
end
|
||||||
|
|
||||||
render_serialized(sidebar_section.reload, SidebarSectionSerializer)
|
render_serialized(sidebar_section, SidebarSectionSerializer)
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
render_json_error(e.record.errors.full_messages.first)
|
render_json_error(e.record.errors.full_messages.first)
|
||||||
rescue Discourse::InvalidAccess
|
rescue Discourse::InvalidAccess
|
||||||
|
@ -71,17 +75,17 @@ class SidebarSectionsController < ApplicationController
|
||||||
when "community"
|
when "community"
|
||||||
sidebar_section.reset_community!
|
sidebar_section.reset_community!
|
||||||
end
|
end
|
||||||
render_serialized(sidebar_section.reload, SidebarSectionSerializer)
|
|
||||||
|
render_serialized(sidebar_section, SidebarSectionSerializer)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reorder
|
def reorder
|
||||||
sidebar_section = SidebarSection.find_by(id: reorder_params["sidebar_section_id"])
|
sidebar_section = SidebarSection.find_by(id: reorder_params["sidebar_section_id"])
|
||||||
@guardian.ensure_can_edit!(sidebar_section)
|
@guardian.ensure_can_edit!(sidebar_section)
|
||||||
|
|
||||||
order = reorder_params["links_order"].map(&:to_i).each_with_index.to_h
|
order = reorder_params["links_order"].map(&:to_i).each_with_index.to_h
|
||||||
|
|
||||||
set_order(sidebar_section, order)
|
set_order(sidebar_section, order)
|
||||||
render json: sidebar_section
|
|
||||||
|
render_serialized_sidebar_section(sidebar_section)
|
||||||
rescue Discourse::InvalidAccess
|
rescue Discourse::InvalidAccess
|
||||||
render json: failed_json, status: 403
|
render json: failed_json, status: 403
|
||||||
end
|
end
|
||||||
|
@ -95,7 +99,8 @@ class SidebarSectionsController < ApplicationController
|
||||||
StaffActionLogger.new(current_user).log_destroy_public_sidebar_section(sidebar_section)
|
StaffActionLogger.new(current_user).log_destroy_public_sidebar_section(sidebar_section)
|
||||||
MessageBus.publish("/refresh-sidebar-sections", nil)
|
MessageBus.publish("/refresh-sidebar-sections", nil)
|
||||||
end
|
end
|
||||||
render json: SidebarSectionSerializer.new(sidebar_section)
|
|
||||||
|
render json: success_json
|
||||||
rescue Discourse::InvalidAccess
|
rescue Discourse::InvalidAccess
|
||||||
render json: failed_json, status: 403
|
render json: failed_json, status: 403
|
||||||
end
|
end
|
||||||
|
@ -127,6 +132,7 @@ class SidebarSectionsController < ApplicationController
|
||||||
.sidebar_section_links
|
.sidebar_section_links
|
||||||
.sort_by { |link| order[link.linkable_id] }
|
.sort_by { |link| order[link.linkable_id] }
|
||||||
.map { |link| link.attributes.merge(position: position_generator.next) }
|
.map { |link| link.attributes.merge(position: position_generator.next) }
|
||||||
|
|
||||||
sidebar_section.sidebar_section_links.upsert_all(links, update_only: [:position])
|
sidebar_section.sidebar_section_links.upsert_all(links, update_only: [:position])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ class SidebarSection < ActiveRecord::Base
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
has_many :sidebar_section_links, -> { order("position") }, dependent: :destroy
|
has_many :sidebar_section_links, -> { order("position") }, dependent: :destroy
|
||||||
|
|
||||||
has_many :sidebar_urls,
|
has_many :sidebar_urls,
|
||||||
through: :sidebar_section_links,
|
through: :sidebar_section_links,
|
||||||
source: :linkable,
|
source: :linkable,
|
||||||
|
|
|
@ -79,7 +79,7 @@ class CurrentUserSerializer < BasicUserSerializer
|
||||||
SidebarSection
|
SidebarSection
|
||||||
.public_sections
|
.public_sections
|
||||||
.or(SidebarSection.where(user_id: object.id))
|
.or(SidebarSection.where(user_id: object.id))
|
||||||
.includes(sidebar_section_links: :linkable)
|
.includes(:sidebar_urls)
|
||||||
.order("(section_type IS NOT NULL) DESC, (public IS TRUE) DESC")
|
.order("(section_type IS NOT NULL) DESC, (public IS TRUE) DESC")
|
||||||
.map { |section| SidebarSectionSerializer.new(section, root: false) }
|
.map { |section| SidebarSectionSerializer.new(section, root: false) }
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ class SidebarSectionSerializer < ApplicationSerializer
|
||||||
attributes :id, :title, :links, :slug, :public, :section_type
|
attributes :id, :title, :links, :slug, :public, :section_type
|
||||||
|
|
||||||
def links
|
def links
|
||||||
object.sidebar_section_links.map { |link| SidebarUrlSerializer.new(link.linkable, root: false) }
|
object.sidebar_urls.map { |sidebar_url| SidebarUrlSerializer.new(sidebar_url, root: false) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def slug
|
def slug
|
||||||
|
|
|
@ -260,7 +260,7 @@ class SiteSerializer < ApplicationSerializer
|
||||||
def anonymous_sidebar_sections
|
def anonymous_sidebar_sections
|
||||||
SidebarSection
|
SidebarSection
|
||||||
.public_sections
|
.public_sections
|
||||||
.includes(sidebar_section_links: :linkable)
|
.includes(:sidebar_urls)
|
||||||
.order("(section_type IS NOT NULL) DESC, (public IS TRUE) DESC")
|
.order("(section_type IS NOT NULL) DESC, (public IS TRUE) DESC")
|
||||||
.map { |section| SidebarSectionSerializer.new(section, root: false) }
|
.map { |section| SidebarSectionSerializer.new(section, root: false) }
|
||||||
end
|
end
|
||||||
|
|
|
@ -71,4 +71,6 @@ Discourse::Application.configure do
|
||||||
if ENV["RAILS_LOG_TO_STDOUT"].present?
|
if ENV["RAILS_LOG_TO_STDOUT"].present?
|
||||||
config.logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
|
config.logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
config.active_record.action_on_strict_loading_violation = :log
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,9 +17,10 @@ RSpec.describe SidebarSection do
|
||||||
community_section.update!(title: "test")
|
community_section.update!(title: "test")
|
||||||
community_section.sidebar_section_links.first.linkable.update!(name: "everything edited")
|
community_section.sidebar_section_links.first.linkable.update!(name: "everything edited")
|
||||||
community_section.sidebar_section_links.last.destroy!
|
community_section.sidebar_section_links.last.destroy!
|
||||||
|
|
||||||
community_section.reset_community!
|
community_section.reset_community!
|
||||||
|
|
||||||
expect(community_section.reload.title).to eq("Community")
|
expect(community_section.reload.title).to eq("Community")
|
||||||
|
|
||||||
expect(community_section.sidebar_section_links.all.map { |link| link.linkable.name }).to eq(
|
expect(community_section.sidebar_section_links.all.map { |link| link.linkable.name }).to eq(
|
||||||
["Everything", "My Posts", "Review", "Admin", "Users", "About", "FAQ", "Groups", "Badges"],
|
["Everything", "My Posts", "Review", "Admin", "Users", "About", "FAQ", "Groups", "Badges"],
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue