posts: optimize modqueue page, status:modqueue, and status:unmoderated searches.

* Optimize status:modqueue and status:unmoderated searches. This brings them down from
  taking 500ms-1000ms per search to ~5ms.

* Change status:unmoderated so that it only filters out the user's disapproved posts, not
  the user's own uploads or past approvals. Now it's equivalent to `status:modqueue -disapproved:evazion`,
  whereas before it was equivalent to `status:modqueue -disapproved:evazion -approver:evazion -user:evazion`.
  Filtering out the user's own uploads and approvals was slow and usually unnecessary,
  since for most users it's rare for their own uploads or approvals to reenter the modqueue.

Before status:modqueue did this:

   SELECT * FROM posts WHERE is_pending = TRUE OR is_flagged = TRUE OR (is_deleted = TRUE AND id IN (SELECT post_id FROM post_appeals WHERE status = 0))

Now we do this:

   SELECT * FROM posts WHERE id IN (SELECT id FROM posts WHERE is_pending = TRUE UNION ALL SELECT id FROM posts WHERE is_flagged = TRUE UNION ALL SELECT id FROM posts WHERE id IN (SELECT post_id FROM post_appeals WHERE status = 0))

Postgres had a bad time with the "pending or flagged or has a pending appeal" clause because
it didn't know that posts can only be in one state at a time, so it overestimated how many
posts would be returned and chose a seq scan. Replacing the OR with a UNION avoids this.
This commit is contained in:
evazion
2022-09-27 06:12:34 -05:00
parent e72073ca6b
commit f49b3c439f
4 changed files with 25 additions and 24 deletions

View File

@@ -3,7 +3,7 @@ require "test_helper"
class PostDisapprovalTest < ActiveSupport::TestCase
context "In all cases" do
setup do
@alice = FactoryBot.create(:moderator_user, name: "alice")
@alice = FactoryBot.create(:moderator_user)
CurrentUser.user = @alice
end
@@ -28,25 +28,18 @@ class PostDisapprovalTest < ActiveSupport::TestCase
end
context "when the current user is alice" do
setup do
CurrentUser.user = @alice
end
should "remove the associated post from alice's moderation queue" do
assert_not(Post.available_for_moderation(CurrentUser.user, hidden: false).map(&:id).include?(@post_1.id))
assert(Post.available_for_moderation(CurrentUser.user, hidden: false).map(&:id).include?(@post_2.id))
assert_not(Post.available_for_moderation(@alice, hidden: false).map(&:id).include?(@post_1.id))
assert(Post.available_for_moderation(@alice, hidden: false).map(&:id).include?(@post_2.id))
end
end
context "when the current user is brittony" do
setup do
@brittony = FactoryBot.create(:moderator_user)
CurrentUser.user = @brittony
end
context "when the current user is not the disapprover" do
should "not remove the associated post from the disapprover's moderation queue" do
@mod = create(:moderator_user)
should "not remove the associated post from brittony's moderation queue" do
assert(Post.available_for_moderation(CurrentUser.user, hidden: false).map(&:id).include?(@post_1.id))
assert(Post.available_for_moderation(CurrentUser.user, hidden: false).map(&:id).include?(@post_2.id))
assert(Post.available_for_moderation(@mod, hidden: false).map(&:id).include?(@post_1.id))
assert(Post.available_for_moderation(@mod, hidden: false).map(&:id).include?(@post_2.id))
end
end
end