Merge pull request 'Fix notification query plan for users with domain blocks' (#7981) from fix/domain-block-subplan-cost into develop

Reviewed-on: https://git.pleroma.social/pleroma/pleroma/pulls/7981
This commit is contained in:
lain 2026-09-02 13:41:40 +00:00
commit fa920037f1
3 changed files with 24 additions and 2 deletions

View file

@ -0,0 +1 @@
Fix notification and timeline queries for users with domain blocks choosing a plan that scans every active user per row; the followed-accounts exception is now an `ARRAY(SELECT ...)` InitPlan that the planner costs once.

View file

@ -258,10 +258,16 @@ defmodule Pleroma.FollowingRelationship do
[_, user_actor: user_actor],
fragment(
# "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)"
#
# ARRAY(SELECT ...) makes the followed-ids lookup an InitPlan that is
# evaluated and costed once. As a plain subselect, the planner charges
# its startup cost on every rescan of the actor lookup, which inflates
# the cost of the index plan enough for it to lose to a plan that
# materialises every active user for every candidate row.
"""
NOT (substring(? from '.*://([^/]*)') = ANY(?)) OR
? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr
ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?)
? = ANY(ARRAY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr
ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?))
""",
user_actor.ap_id,
^user.domain_blocks,

View file

@ -1028,6 +1028,21 @@ defmodule Pleroma.NotificationTest do
assert Notification.for_user(user) == []
end
test "it doesn't return notifications from a blocked domain unless the actor is followed",
%{user: user} do
blocked_domain = "blocked.example"
stranger = insert(:user, local: false, domain: blocked_domain)
followed = insert(:user, local: false, domain: blocked_domain)
{:ok, user, followed} = User.follow(user, followed)
{:ok, user} = User.block_domain(user, blocked_domain)
{:ok, _} = CommonAPI.post(stranger, %{status: "hey @#{user.nickname}"})
{:ok, followed_activity} = CommonAPI.post(followed, %{status: "hey @#{user.nickname}"})
assert [%{activity: %{id: followed_id}}] = Notification.for_user(user)
assert followed_id == followed_activity.id
end
test "it doesn't return notifications for domain-blocked non-followed user", %{user: user} do
blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com")