mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
PERF: Load users in batches when generating notifications (#8870)
Previously, `notify_first_post_users` was loading all users into memory simultaneously, which can cause Sidekiq to run out of memory for large sites. `notify_post_users` was loading every user one-by-one in a loop. This commit makes both these functions load users in batches of 100. This should make the memory usage of `notify_first_post_users` lower, and reduce the number of queries required in `notify_post_users`.
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PostAlerter
|
class PostAlerter
|
||||||
|
USER_BATCH_SIZE = 100
|
||||||
|
|
||||||
def self.post_created(post, opts = {})
|
def self.post_created(post, opts = {})
|
||||||
PostAlerter.new(opts).after_save_post(post, true)
|
PostAlerter.new(opts).after_save_post(post, true)
|
||||||
post
|
post
|
||||||
@@ -140,7 +142,7 @@ class PostAlerter
|
|||||||
users = User.where(id: user_ids)
|
users = User.where(id: user_ids)
|
||||||
|
|
||||||
DiscourseEvent.trigger(:before_create_notifications_for_users, users, post)
|
DiscourseEvent.trigger(:before_create_notifications_for_users, users, post)
|
||||||
users.each do |user|
|
each_user_in_batches(users) do |user|
|
||||||
create_notification(user, Notification.types[:watching_first_post], post)
|
create_notification(user, Notification.types[:watching_first_post], post)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -609,11 +611,10 @@ class PostAlerter
|
|||||||
|
|
||||||
DiscourseEvent.trigger(:before_create_notifications_for_users, notify, post)
|
DiscourseEvent.trigger(:before_create_notifications_for_users, notify, post)
|
||||||
|
|
||||||
already_seen_users = TopicUser.where(topic_id: post.topic.id).where("highest_seen_post_number >= ?", post.post_number).pluck(:user_id)
|
already_seen_user_ids = Set.new TopicUser.where(topic_id: post.topic.id).where("highest_seen_post_number >= ?", post.post_number).pluck(:user_id)
|
||||||
|
|
||||||
notify.pluck(:id).each do |user_id|
|
each_user_in_batches(notify) do |user|
|
||||||
notification_type = already_seen_users.include?(user_id) ? Notification.types[:edited] : Notification.types[:posted]
|
notification_type = already_seen_user_ids.include?(user.id) ? Notification.types[:edited] : Notification.types[:posted]
|
||||||
user = User.find_by(id: user_id)
|
|
||||||
create_notification(user, notification_type, post)
|
create_notification(user, notification_type, post)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -621,4 +622,13 @@ class PostAlerter
|
|||||||
def warn_if_not_sidekiq
|
def warn_if_not_sidekiq
|
||||||
Rails.logger.warn("PostAlerter.#{caller_locations(1, 1)[0].label} was called outside of sidekiq") unless Sidekiq.server?
|
Rails.logger.warn("PostAlerter.#{caller_locations(1, 1)[0].label} was called outside of sidekiq") unless Sidekiq.server?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def each_user_in_batches(users)
|
||||||
|
# This is race-condition-safe, unlike #find_in_batches
|
||||||
|
users.pluck(:id).each_slice(USER_BATCH_SIZE) do |user_ids_batch|
|
||||||
|
User.where(id: user_ids_batch).each { |user| yield(user) }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user