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.
This commit is contained in:
Rafael dos Santos Silva 2021-11-10 13:22:26 -03:00 committed by GitHub
parent b72688340f
commit d4e35f50c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 2 deletions

View File

@ -1598,6 +1598,12 @@ export default Controller.extend(bufferedProperty("model"), {
.then(() => refresh({ id: data.id, refreshLikes: true })); .then(() => refresh({ id: data.id, refreshLikes: true }));
break; break;
} }
case "liked": {
postStream
.triggerLikedPost(data.id, data.likes_count)
.then(() => refresh({ id: data.id, refreshLikes: true }));
break;
}
case "revised": case "revised":
case "rebaked": { case "rebaked": {
postStream postStream

View File

@ -847,6 +847,18 @@ export default RestModel.extend({
return resolved; 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) { triggerReadPost(postId, readersCount) {
const resolved = Promise.resolve(); const resolved = Promise.resolve();
resolved.then(() => { resolved.then(() => {

View File

@ -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) { revertToRevision(version) {
return ajax(`/posts/${this.id}/revisions/${version}/revert`, { return ajax(`/posts/${this.id}/revisions/${version}/revert`, {
type: "PUT", type: "PUT",

View File

@ -156,13 +156,15 @@ private
end end
def notify_subscribers 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 @post.publish_change_to_clients! :acted
end end
end end
def self.notify_types def self.notify_types
@notify_types ||= ([:like] + PostActionType.notify_flag_types.keys) @notify_types ||= PostActionType.notify_flag_types.keys
end end
def enforce_rules def enforce_rules