PERF: defer user notifications (#5827)

This commit is contained in:
Régis Hanol 2018-05-15 09:51:32 +02:00 committed by GitHub
parent 02e0426de6
commit 93ed8d2522
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 87 additions and 74 deletions

View File

@ -3,8 +3,7 @@ require_dependency 'user_action_creator'
class PostAlerter class PostAlerter
def self.post_created(post, opts = {}) def self.post_created(post, opts = {})
alerter = PostAlerter.new(opts) PostAlerter.new(opts).after_save_post(post, true)
alerter.after_save_post(post, true)
post post
end end
@ -90,30 +89,8 @@ class PostAlerter
# private messages # private messages
if new_record if new_record
if post.topic.private_message? if post.topic.private_message?
# users that aren't part of any mentioned groups notify_pm_users(post, reply_to_user, notified)
users = directly_targeted_users(post).reject { |u| notified.include?(u) }
DiscourseEvent.trigger(:before_create_notifications_for_users, users, post)
users.each do |user|
notification_level = TopicUser.get(post.topic, user).try(:notification_level)
if reply_to_user == user || notification_level == TopicUser.notification_levels[:watching] || user.staged?
create_notification(user, Notification.types[:private_message], post)
end
end
# users that are part of all mentionned groups
users = indirectly_targeted_users(post).reject { |u| notified.include?(u) }
DiscourseEvent.trigger(:before_create_notifications_for_users, users, post)
users.each do |user|
# only create a notification when watching the group
notification_level = TopicUser.get(post.topic, user).try(:notification_level)
if notification_level == TopicUser.notification_levels[:watching]
create_notification(user, Notification.types[:private_message], post)
elsif notification_level == TopicUser.notification_levels[:tracking]
notify_group_summary(user, post)
end
end
elsif post.post_type == Post.types[:regular] elsif post.post_type == Post.types[:regular]
# If it's not a private message and it's not an automatic post caused by a moderator action, notify the users
notify_post_users(post, notified) notify_post_users(post, notified)
end end
end end
@ -153,10 +130,12 @@ class PostAlerter
# Don't notify the OP # Don't notify the OP
user_ids -= [post.user_id] user_ids -= [post.user_id]
users = User.where(id: user_ids) users = User.where(id: user_ids)
DiscourseEvent.trigger(:before_create_notifications_for_users, users, post)
users.each do |user| Scheduler::Defer.later("Notify First Post Watchers") do
create_notification(user, Notification.types[:watching_first_post], post) DiscourseEvent.trigger(:before_create_notifications_for_users, users, post)
users.each do |user|
create_notification(user, Notification.types[:watching_first_post], post)
end
end end
end end
@ -222,7 +201,6 @@ class PostAlerter
end end
def notify_group_summary(user, post) def notify_group_summary(user, post)
@group_stats ||= {} @group_stats ||= {}
stats = (@group_stats[post.topic_id] ||= group_stats(post.topic)) stats = (@group_stats[post.topic_id] ||= group_stats(post.topic))
return unless stats return unless stats
@ -262,12 +240,9 @@ class PostAlerter
end end
def should_notify_like?(user, notification) def should_notify_like?(user, notification)
return true if user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:always] return true if user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:always]
return true if user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:first_time_and_daily] && notification.created_at < 1.day.ago return true if user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:first_time_and_daily] && notification.created_at < 1.day.ago
false
return false
end end
def should_notify_previous?(user, notification, opts) def should_notify_previous?(user, notification, opts)
@ -504,59 +479,101 @@ class PostAlerter
users = [users] unless users.is_a?(Array) users = [users] unless users.is_a?(Array)
users = users.reject { |u| u.staged? } if post.topic&.private_message? users = users.reject { |u| u.staged? } if post.topic&.private_message?
DiscourseEvent.trigger(:before_create_notifications_for_users, users, post) Scheduler::Defer.later("Notify Users") do
users.each do |u| DiscourseEvent.trigger(:before_create_notifications_for_users, users, post)
create_notification(u, Notification.types[type], post, opts) users.each do |u|
create_notification(u, Notification.types[type], post, opts)
end
end end
users users
end end
def notify_pm_users(post, reply_to_user, notified)
return unless post.topic
Scheduler::Defer.later("Notify PM Users") do
# users that aren't part of any mentioned groups
users = directly_targeted_users(post).reject { |u| notified.include?(u) }
DiscourseEvent.trigger(:before_create_notifications_for_users, users, post)
users.each do |user|
notification_level = TopicUser.get(post.topic, user)&.notification_level
if reply_to_user == user || notification_level == TopicUser.notification_levels[:watching] || user.staged?
create_notification(user, Notification.types[:private_message], post)
end
end
# users that are part of all mentionned groups
users = indirectly_targeted_users(post).reject { |u| notified.include?(u) }
DiscourseEvent.trigger(:before_create_notifications_for_users, users, post)
users.each do |user|
case TopicUser.get(post.topic, user)&.notification_level
when TopicUser.notification_levels[:watching]
# only create a notification when watching the group
create_notification(user, Notification.types[:private_message], post)
when TopicUser.notification_levels[:tracking]
notify_group_summary(user, post)
end
end
end
end
def notify_post_users(post, notified) def notify_post_users(post, notified)
return unless post.topic return unless post.topic
condition = <<SQL condition = <<~SQL
id IN (
SELECT user_id
FROM topic_users
WHERE notification_level = :watching
AND topic_id = :topic_id
id IN ( UNION
SELECT user_id FROM topic_users
WHERE notification_level = :watching AND topic_id = :topic_id
UNION ALL SELECT cu.user_id
FROM category_users cu
LEFT JOIN topic_users tu ON tu.user_id = cu.user_id
AND tu.topic_id = :topic_id
WHERE cu.notification_level = :watching
AND cu.category_id = :category_id
AND tu.user_id IS NULL
SELECT cu.user_id FROM category_users cu /*tags*/
LEFT JOIN topic_users tu ON tu.user_id = cu.user_id AND tu.topic_id = :topic_id )
WHERE cu.notification_level = :watching AND cu.category_id = :category_id AND tu.user_id IS NULL SQL
/*tags*/
)
SQL
tag_ids = post.topic.topic_tags.pluck('topic_tags.tag_id') tag_ids = post.topic.topic_tags.pluck('topic_tags.tag_id')
if tag_ids.present?
condition.sub! "/*tags*/", <<SQL
UNION ALL
SELECT tag_users.user_id FROM tag_users if tag_ids.present?
LEFT JOIN topic_users tu ON tu.user_id = tag_users.user_id AND tu.topic_id = :topic_id condition.sub! "/*tags*/", <<~SQL
WHERE tag_users.notification_level = :watching AND tag_users.tag_id IN (:tag_ids) AND tu.user_id IS NULL UNION
SQL
SELECT tag_users.user_id
FROM tag_users
LEFT JOIN topic_users tu ON tu.user_id = tag_users.user_id
AND tu.topic_id = :topic_id
WHERE tag_users.notification_level = :watching
AND tag_users.tag_id IN (:tag_ids)
AND tu.user_id IS NULL
SQL
end end
notify = User.where(condition, notify = User.where(condition,
watching: TopicUser.notification_levels[:watching], watching: TopicUser.notification_levels[:watching],
topic_id: post.topic_id, topic_id: post.topic_id,
category_id: post.topic.category_id, category_id: post.topic.category_id,
tag_ids: tag_ids tag_ids: tag_ids
) )
exclude_user_ids = notified.map(&:id) exclude_user_ids = notified.map(&:id)
notify = notify.where("id NOT IN (?)", exclude_user_ids) if exclude_user_ids.present? notify = notify.where("id NOT IN (?)", exclude_user_ids) if exclude_user_ids.present?
DiscourseEvent.trigger(:before_create_notifications_for_users, notify, post) Scheduler::Defer.later("Notify Post Users") do
DiscourseEvent.trigger(:before_create_notifications_for_users, notify, post)
notify.pluck(:id).each do |user_id| notify.pluck(:id).each do |user_id|
user = User.find_by(id: user_id) user = User.find_by(id: user_id)
create_notification(user, Notification.types[:posted], post) create_notification(user, Notification.types[:posted], post)
end
end end
end end

View File

@ -28,7 +28,7 @@ module Scheduler
def later(desc = nil, db = RailsMultisite::ConnectionManagement.current_db, &blk) def later(desc = nil, db = RailsMultisite::ConnectionManagement.current_db, &blk)
if @async if @async
start_thread unless (@thread && @thread.alive?) || @paused start_thread unless @thread&.alive? || @paused
@queue << [db, blk, desc] @queue << [db, blk, desc]
else else
blk.call blk.call
@ -36,13 +36,13 @@ module Scheduler
end end
def stop! def stop!
@thread.kill if @thread && @thread.alive? @thread.kill if @thread&.alive?
@thread = nil @thread = nil
end end
# test only # test only
def stopped? def stopped?
!(@thread && @thread.alive?) !@thread&.alive?
end end
def do_all_work def do_all_work
@ -55,12 +55,8 @@ module Scheduler
def start_thread def start_thread
@mutex.synchronize do @mutex.synchronize do
return if @thread && @thread.alive? return if @thread&.alive?
@thread = Thread.new { @thread = Thread.new { do_work while true }
while true
do_work
end
}
end end
end end