From f268eccaa05365833c703ccafa10129ea58af3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 14 Aug 2024 18:11:24 +0200 Subject: [PATCH] PERF: removed N*2+1 in availability topic (#599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WE "recently" did a few changes in the way we use the availability topic - We used to have 1 topic per year, but have now moved to just using one topic and deleted past posts - We used to delete past automatic holidays, but we don't anymore since we use them to keep track of #leave days used - There are more employees covering more regions All three changes made the query that list all the events to be displayed in the availability topic go 💥 It used to be - 1 query to list all events from 6 months ago up to 6 months in the future - 1 query for every events to load the associated user - 1 query for every events to load the associated's user timezone So basically, a N*2 + 1 😅 This commit fixes the issue by doing - 1 query to get all "standalone" events (those associated to a post) - 1 query to get all the users timezones (just the ones who have an holiday in the current period). - 1 query to get all the "automatic" events (aka. holidays that are comming from the holidays gem). So we went from N*2 + 1 down to 3 🚀 --- plugin.rb | 103 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 37 deletions(-) diff --git a/plugin.rb b/plugin.rb index 09ae24971..19d3a2f36 100644 --- a/plugin.rb +++ b/plugin.rb @@ -358,52 +358,81 @@ module ::DiscoursePostEvent end add_to_serializer(:post, :calendar_details, include_condition: -> { object.is_first_post? }) do - grouped = {} - standalones = [] - - CalendarEvent - .where(topic_id: object.topic_id) - .where("post_id IS NOT NULL OR start_date >= ?", 6.months.ago) - .order(:start_date, :end_date) - .each do |event| - if event.post_id - standalones << { + start_date = 6.months.ago + + standalone_sql = <<~SQL + SELECT post_number, description, start_date, end_date, username, recurrence, timezone + FROM calendar_events + WHERE topic_id = :topic_id + AND post_id IS NOT NULL + ORDER BY start_date, end_date + SQL + + standalones = + DB + .query(standalone_sql, topic_id: object.topic_id) + .map do |row| + { type: :standalone, - post_number: event.post_number, - message: event.description, - from: event.start_date, - to: event.end_date, - username: event.username, - recurring: event.recurrence, - post_url: Post.url("-", event.topic_id, event.post_number), - timezone: event.timezone, - } - else - identifier = "#{event.region.split("_").first}-#{event.start_date.strftime("%Y-%j")}" - - grouped[identifier] ||= { - type: :grouped, - from: event.start_date, - timezone: event.timezone, - name: [], - users: [], + post_number: row.post_number, + message: row.description, + from: row.start_date, + to: row.end_date, + username: row.username, + recurring: row.recurrence, + post_url: Post.url("-", object.topic_id, row.post_number), + timezone: row.timezone, } + end - user = User.find_by_username(event.username) + timezones = + UserOption + .where( + user_id: + CalendarEvent.where( + topic_id: object.topic_id, + post_id: nil, + start_date: start_date.., + ).select(:user_id), + ) + .where("LENGTH(COALESCE(timezone, '')) > 0") + .pluck(:user_id, :timezone) + .to_h - grouped[identifier][:name] << event.description - grouped[identifier][:users] << { - username: event.username, - timezone: user.present? ? user.user_option.timezone : nil, - } - end + grouped = {} + + grouped_sql = <<~SQL + SELECT region, start_date, timezone, user_id, username, description + FROM calendar_events + WHERE topic_id = :topic_id + AND post_id IS NULL + AND start_date >= :start_date + ORDER BY region, start_date + SQL + + DB + .query(grouped_sql, topic_id: object.topic_id, start_date: start_date) + .each do |row| + identifier = "#{row.region.split("_").first}-#{row.start_date.strftime("%Y-%j")}" + + grouped[identifier] ||= { + type: :grouped, + from: row.start_date, + timezone: row.timezone, + name: [], + users: [], + } + + grouped[identifier][:name] << row.description + grouped[identifier][:users] << { username: row.username, timezone: timezones[row.user_id] } end grouped.each do |_, v| - v[:name].sort!.uniq! + v[:name].uniq! + v[:name].sort! v[:name] = v[:name].join(", ") - v[:users].sort! { |a, b| a[:username] <=> b[:username] } v[:users].uniq! { |u| u[:username] } + v[:users].sort! { |a, b| a[:username] <=> b[:username] } end standalones + grouped.values