From d4e35f50c25401bacb0ba0fd81732135162421a9 Mon Sep 17 00:00:00 2001 From: Rafael dos Santos Silva Date: Wed, 10 Nov 2021 13:22:26 -0300 Subject: [PATCH] PERF: Update like count in visible posts without an extra GET per like (#14869) PERF: Update like count in visible posts without an extra GET per like Currently when a user is reading a topic and some post in it receive a like from another user, the Ember app will be notified via MessageBus and issue a GET to `/posts/{id}` to get the new like count. This worked fine for us until today, but it can easily create a self-inflicted DDoS when a topic with a large number of visitors gets a large number of likes, since we will issue `visitors * likes` GET requests requests. This patch optimizes this flow, by sending the new like count down in the MessageBus notification, removing any need for the extra request. It shouldn't cause any drift on the count because we send down the full count instead of the difference too. Possible follow-ups could include handling like removal. --- .../discourse/app/controllers/topic.js | 6 ++++ .../discourse/app/models/post-stream.js | 12 +++++++ .../javascripts/discourse/app/models/post.js | 31 +++++++++++++++++++ lib/post_action_creator.rb | 6 ++-- 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js index f625c28da3b..2382a2688bc 100644 --- a/app/assets/javascripts/discourse/app/controllers/topic.js +++ b/app/assets/javascripts/discourse/app/controllers/topic.js @@ -1598,6 +1598,12 @@ export default Controller.extend(bufferedProperty("model"), { .then(() => refresh({ id: data.id, refreshLikes: true })); break; } + case "liked": { + postStream + .triggerLikedPost(data.id, data.likes_count) + .then(() => refresh({ id: data.id, refreshLikes: true })); + break; + } case "revised": case "rebaked": { postStream diff --git a/app/assets/javascripts/discourse/app/models/post-stream.js b/app/assets/javascripts/discourse/app/models/post-stream.js index fe51c537a84..c2b64b3579f 100644 --- a/app/assets/javascripts/discourse/app/models/post-stream.js +++ b/app/assets/javascripts/discourse/app/models/post-stream.js @@ -847,6 +847,18 @@ export default RestModel.extend({ return resolved; }, + triggerLikedPost(postId, likesCount) { + const resolved = Promise.resolve(); + + const post = this.findLoadedPost(postId); + if (post) { + post.updateLikeCount(likesCount); + this.storePost(post); + } + + return resolved; + }, + triggerReadPost(postId, readersCount) { const resolved = Promise.resolve(); resolved.then(() => { diff --git a/app/assets/javascripts/discourse/app/models/post.js b/app/assets/javascripts/discourse/app/models/post.js index a6da404962d..16ea1d81532 100644 --- a/app/assets/javascripts/discourse/app/models/post.js +++ b/app/assets/javascripts/discourse/app/models/post.js @@ -355,6 +355,37 @@ const Post = RestModel.extend({ } }, + updateLikeCount(count) { + let current_actions_summary = this.get("actions_summary"); + let likeActionID = Site.current().post_action_types.find( + (a) => a.name_key === "like" + ).id; + + if (!this.actions_summary.find((entry) => entry.id === likeActionID)) { + let json = Post.munge({ + id: this.id, + actions_summary: [ + { + id: likeActionID, + count, + }, + ], + }); + this.set( + "actions_summary", + Object.assign(current_actions_summary, json.actions_summary) + ); + this.set("actionByName", json.actionByName); + this.set("likeAction", json.likeAction); + } else { + this.actions_summary.find( + (entry) => entry.id === likeActionID + ).count = count; + this.actionByName["like"] = count; + this.likeAction.count = count; + } + }, + revertToRevision(version) { return ajax(`/posts/${this.id}/revisions/${version}/revert`, { type: "PUT", diff --git a/lib/post_action_creator.rb b/lib/post_action_creator.rb index e1f42eef74c..31a037844b1 100644 --- a/lib/post_action_creator.rb +++ b/lib/post_action_creator.rb @@ -156,13 +156,15 @@ private end def notify_subscribers - if self.class.notify_types.include?(@post_action_name) + if @post_action_name == :like + @post.publish_change_to_clients! :liked, { likes_count: @post.like_count + 1 } + elsif self.class.notify_types.include?(@post_action_name) @post.publish_change_to_clients! :acted end end def self.notify_types - @notify_types ||= ([:like] + PostActionType.notify_flag_types.keys) + @notify_types ||= PostActionType.notify_flag_types.keys end def enforce_rules