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 (#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.

diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js
index f625c28..2382a26 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 fe51c53..c2b64b3 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 a6da404..16ea1d8 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 e1f42ee..31a0378 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

GitHub sha: d4e35f50c25401bacb0ba0fd81732135162421a9

This commit appears in #14869 which was approved by martin. It was merged by Falco.