comments: fewer db round trips on creation

Writes a vote directly to avoid vote_thusly doing round trips to check if one
exists, etc.

Removes redundant transactions from controllers from #899. Rails already creates
a transaction for the .save.

Unifies Story cache updating. Previously recalculate_hotness! was called twice
on comment creation. Moves comment counting into the db.

Shorter transaction should reduce the frequence of
lobsters/lobsters-ansible/issues/39 but seems unlikely to eliminate it as the
create + upvote transactions for stories + comments still read/write from
stories, comments, and votes.
This commit is contained in:
Peter Bhat Harkins 2023-10-06 23:32:31 -05:00
parent d806f7a60d
commit d13c6c4676
5 changed files with 19 additions and 33 deletions

View File

@ -51,7 +51,7 @@ class CommentsController < ApplicationController
content_type: "text/html", locals: {comment: comment} content_type: "text/html", locals: {comment: comment}
end end
if comment.valid? && params[:preview].blank? && ActiveRecord::Base.transaction { comment.save } if comment.valid? && params[:preview].blank? && comment.save
comment.current_vote = {vote: 1} comment.current_vote = {vote: 1}
render_created_comment(comment) render_created_comment(comment)
else else

View File

@ -20,7 +20,7 @@ class StoriesController < ApplicationController
@story.attributes = story_params @story.attributes = story_params
if @story.valid? && !(@story.already_posted_recently? && !@story.seen_previous) if @story.valid? && !(@story.already_posted_recently? && !@story.seen_previous)
if ActiveRecord::Base.transaction { @story.save } if @story.save
ReadRibbon.where(user: @user, story: @story).first_or_create ReadRibbon.where(user: @user, story: @story).first_or_create
return redirect_to @story.comments_path return redirect_to @story.comments_path
end end

View File

@ -29,10 +29,6 @@ class Comment < ApplicationRecord
end end
after_create :record_initial_upvote, :mark_submitter, :deliver_reply_notifications, after_create :record_initial_upvote, :mark_submitter, :deliver_reply_notifications,
:deliver_mention_notifications, :log_hat_use :deliver_mention_notifications, :log_hat_use
after_create do
# fire this once after record_initial_upvote
update_score_and_recalculate! 0, 0
end
after_destroy :unassign_votes after_destroy :unassign_votes
scope :deleted, -> { where(is_deleted: true) } scope :deleted, -> { where(is_deleted: true) }
@ -259,7 +255,7 @@ class Comment < ApplicationRecord
save(validate: false) save(validate: false)
Comment.record_timestamps = true Comment.record_timestamps = true
story.update_comments_count! story.update_cached_columns
self.user.refresh_counts! self.user.refresh_counts!
end end
@ -371,7 +367,7 @@ class Comment < ApplicationRecord
confidence_order = concat(lpad(char(65536 - floor(((confidence - -0.2) * 65535) / 1.2) using binary), 2, '0'), char(id & 0xff using binary)) confidence_order = concat(lpad(char(65536 - floor(((confidence - -0.2) * 65535) / 1.2) using binary), 2, '0'), char(id & 0xff using binary))
WHERE id = #{id.to_i} WHERE id = #{id.to_i}
SQL SQL
story.recalculate_hotness! story.update_cached_columns
end end
def gone_text def gone_text
@ -504,11 +500,11 @@ class Comment < ApplicationRecord
end end
def record_initial_upvote def record_initial_upvote
Vote.vote_thusly_on_story_or_comment_for_user_because( # not calling vote_thusly to save round-trips of validations
1, story_id, id, user_id, nil, false Vote.create! story: story, comment: self, user: user, vote: 1
) update_score_and_recalculate! 0, 0 # trigger db calculation
story.update_comments_count! story.update_cached_columns
end end
def score_for_user(u) def score_for_user(u)
@ -541,7 +537,7 @@ class Comment < ApplicationRecord
end end
def unassign_votes def unassign_votes
story.update_comments_count! story.update_cached_columns
end end
def url def url
@ -592,7 +588,7 @@ class Comment < ApplicationRecord
save(validate: false) save(validate: false)
Comment.record_timestamps = true Comment.record_timestamps = true
story.update_comments_count! story.update_cached_columns
self.user.refresh_counts! self.user.refresh_counts!
end end

View File

@ -157,7 +157,7 @@ class Story < ApplicationRecord
before_save :log_moderation before_save :log_moderation
before_save :fix_bogus_chars before_save :fix_bogus_chars
after_create :mark_submitter, :record_initial_upvote after_create :mark_submitter, :record_initial_upvote
after_save :update_merged_into_story_comments, :recalculate_hotness!, :update_story_text after_save :update_cached_columns, :update_story_text
validate do validate do
if url.present? if url.present?
@ -270,8 +270,8 @@ class Story < ApplicationRecord
def self.recalculate_all_hotnesses! def self.recalculate_all_hotnesses!
# do the front page first, since find_each can't take an order # do the front page first, since find_each can't take an order
Story.order("id DESC").limit(100).each(&:recalculate_hotness!) Story.order("id DESC").limit(100).each(&:update_cached_columns)
Story.find_each(&:recalculate_hotness!) Story.find_each(&:update_cached_columns)
true true
end end
@ -620,10 +620,6 @@ class Story < ApplicationRecord
merged_story_id ? merged_into_story.try(:short_id) : nil merged_story_id ? merged_into_story.try(:short_id) : nil
end end
def recalculate_hotness!
update_column :hotness, calculated_hotness
end
def record_initial_upvote def record_initial_upvote
Vote.vote_thusly_on_story_or_comment_for_user_because(1, id, nil, user_id, nil, false) Vote.vote_thusly_on_story_or_comment_for_user_because(1, id, nil, user_id, nil, false)
end end
@ -812,17 +808,11 @@ class Story < ApplicationRecord
end end
end end
def update_comments_count! def update_cached_columns
comments = merged_comments update_column :comments_count, merged_comments.active.count
merged_into_story&.update_cached_columns
# calculate count after removing deleted comments and threads update_column :hotness, calculated_hotness
update_column :comments_count, (comments.count { |c| !c.is_gone? })
update_merged_into_story_comments
recalculate_hotness!
end
def update_merged_into_story_comments
merged_into_story&.update_comments_count!
end end
def update_story_text def update_story_text

View File

@ -405,7 +405,7 @@ describe Story do
end end
end end
describe "#update_comments_count!" do describe "#update_cached_columns" do
context "with a merged_into_story" do context "with a merged_into_story" do
let(:merged_into_story) { create(:story) } let(:merged_into_story) { create(:story) }
let(:story) { create(:story, merged_into_story: merged_into_story) } let(:story) { create(:story, merged_into_story: merged_into_story) }
@ -414,7 +414,7 @@ describe Story do
expect(story.comments_count).to eq 0 expect(story.comments_count).to eq 0
expect(merged_into_story.comments_count).to eq 0 expect(merged_into_story.comments_count).to eq 0
create(:comment, story: story) create(:comment, story: story)
story.update_comments_count! story.update_cached_columns
expect(story.comments_count).to eq 1 expect(story.comments_count).to eq 1
expect(merged_into_story.comments_count).to eq 1 expect(merged_into_story.comments_count).to eq 1
end end