favorites: merge favorites subtables.

Merge the 100 favorite subtables into a single table.

Previously the favorites table was partitioned by user id into 100
subtables to try to make searching by user id faster. This wasn't really
necessary and probably slower than just making an index on
(favorites.user_id, favorites.id) to satisfy ordfav searches. BTree
indexes are logarithmic so dividing an index by 100 doesn't make it 100
times faster to search; instead it just removes a layer or two from the
tree.

This also adds a uniqueness index on (user_id, post_id) to prevent
duplicate favorites. Previously we had to check for duplicates at the
application layer, which required careful locking to do it correctly.

Finally, this adds an index on favorites.id, which was surprisingly
missing before. This made ordering and deleting favorites by id really
slow because it degraded to a sequential scan.
This commit is contained in:
evazion
2021-10-08 10:22:57 -05:00
parent 73acc16271
commit 340e1008e9
6 changed files with 58 additions and 3422 deletions

View File

@@ -431,8 +431,7 @@ class PostQueryBuilder
favuser = User.find_by_name(username)
if favuser.present? && Pundit.policy!(current_user, favuser).can_see_favorites?
favorites = Favorite.from("favorites_#{favuser.id % 100} AS favorites").where(user: favuser)
Post.where(id: favorites.select(:post_id))
Post.where(id: favuser.favorites.select(:post_id))
else
Post.none
end
@@ -509,6 +508,8 @@ class PostQueryBuilder
relation = search_order(relation, "created_at_desc")
elsif find_metatag(:order) == "custom"
relation = search_order_custom(relation, select_metatags(:id).map(&:value))
elsif has_metatag?(:ordfav)
# no-op
else
relation = search_order(relation, find_metatag(:order))
end

View File

@@ -4,7 +4,7 @@ class Favorite < ApplicationRecord
belongs_to :post
belongs_to :user
scope :for_user, ->(user_id) { where("favorites.user_id % 100 = ? AND favorites.user_id = ?", user_id.to_i % 100, user_id) }
scope :for_user, ->(user_id) { where(user_id: user_id) }
scope :public_favorites, -> { where(user: User.bit_prefs_match(:enable_private_favorites, false)) }
def self.visible(user)

View File

@@ -138,7 +138,7 @@ class User < ApplicationRecord
has_many :forum_posts, -> {order("forum_posts.created_at, forum_posts.id")}, :foreign_key => "creator_id"
has_many :user_name_change_requests, -> {order("user_name_change_requests.created_at desc")}
has_many :favorite_groups, -> {order(name: :asc)}, foreign_key: :creator_id
has_many :favorites, ->(rec) {where("user_id % 100 = #{rec.id % 100} and user_id = #{rec.id}").order("id desc")}
has_many :favorites
has_many :ip_bans, foreign_key: :creator_id
has_many :tag_aliases, foreign_key: :creator_id
has_many :tag_implications, foreign_key: :creator_id