Preloading of follow relations for timeline/statuses rendering (performance improvement). Refactoring.

This commit is contained in:
Ivan Tashkinov 2020-03-23 12:01:11 +03:00
parent c2e415143b
commit 3c78e5f327
5 changed files with 130 additions and 37 deletions

View file

@ -129,4 +129,30 @@ def move_following(origin, target) do
move_following(origin, target) move_following(origin, target)
end end
end end
def all_between_user_sets(
source_users,
target_users
)
when is_list(source_users) and is_list(target_users) do
get_bin_ids = fn user ->
with {:ok, bin_id} <- CompatType.dump(user.id), do: bin_id
end
source_user_ids = Enum.map(source_users, &get_bin_ids.(&1))
target_user_ids = Enum.map(target_users, &get_bin_ids.(&1))
__MODULE__
|> where(
fragment(
"(follower_id = ANY(?) AND following_id = ANY(?)) OR \
(follower_id = ANY(?) AND following_id = ANY(?))",
^source_user_ids,
^target_user_ids,
^target_user_ids,
^source_user_ids
)
)
|> Repo.all()
end
end end

View file

@ -674,7 +674,14 @@ def unfollow(%User{} = follower, %User{} = followed) do
def get_follow_state(%User{} = follower, %User{} = following) do def get_follow_state(%User{} = follower, %User{} = following) do
following_relationship = FollowingRelationship.get(follower, following) following_relationship = FollowingRelationship.get(follower, following)
get_follow_state(follower, following, following_relationship)
end
def get_follow_state(
%User{} = follower,
%User{} = following,
following_relationship
) do
case {following_relationship, following.local} do case {following_relationship, following.local} do
{nil, false} -> {nil, false} ->
case Utils.fetch_latest_follow(follower, following) do case Utils.fetch_latest_follow(follower, following) do

View file

@ -116,6 +116,19 @@ def dictionary(
|> Repo.all() |> Repo.all()
end end
def exists?(dictionary, rel_type, source, target, func) do
cond do
is_nil(source) or is_nil(target) ->
false
dictionary ->
[rel_type, source.id, target.id] in dictionary
true ->
func.(source, target)
end
end
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
changeset changeset
|> validate_change(:target_id, fn _, target_id -> |> validate_change(:target_id, fn _, target_id ->

View file

@ -6,21 +6,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
def test_rel(user_relationships, rel_type, source, target, func) do defp find_following_rel(following_relationships, follower, following) do
cond do Enum.find(following_relationships, fn
is_nil(source) or is_nil(target) -> fr -> fr.follower_id == follower.id and fr.following_id == following.id
false end)
user_relationships ->
[rel_type, source.id, target.id] in user_relationships
true ->
func.(source, target)
end
end end
def render("index.json", %{users: users} = opts) do def render("index.json", %{users: users} = opts) do
@ -53,21 +47,61 @@ def render(
%{user: %User{} = reading_user, target: %User{} = target} = opts %{user: %User{} = reading_user, target: %User{} = target} = opts
) do ) do
user_relationships = Map.get(opts, :user_relationships) user_relationships = Map.get(opts, :user_relationships)
following_relationships = opts[:following_relationships]
follow_state = User.get_follow_state(reading_user, target) follow_state =
if following_relationships do
user_to_target_following_relation =
find_following_rel(following_relationships, reading_user, target)
User.get_follow_state(reading_user, target, user_to_target_following_relation)
else
User.get_follow_state(reading_user, target)
end
followed_by =
if following_relationships do
with %{state: "accept"} <-
find_following_rel(following_relationships, target, reading_user) do
true
else
_ -> false
end
else
User.following?(target, reading_user)
end
# TODO: add a note on adjusting StatusView.user_relationships_opt/1 re: preloading of user relations # TODO: add a note on adjusting StatusView.user_relationships_opt/1 re: preloading of user relations
%{ %{
id: to_string(target.id), id: to_string(target.id),
following: follow_state == "accept", following: follow_state == "accept",
followed_by: User.following?(target, reading_user), followed_by: followed_by,
blocking: blocking:
test_rel(user_relationships, :block, reading_user, target, &User.blocks_user?(&1, &2)), UserRelationship.exists?(
user_relationships,
:block,
reading_user,
target,
&User.blocks_user?(&1, &2)
),
blocked_by: blocked_by:
test_rel(user_relationships, :block, target, reading_user, &User.blocks_user?(&1, &2)), UserRelationship.exists?(
muting: test_rel(user_relationships, :mute, reading_user, target, &User.mutes?(&1, &2)), user_relationships,
:block,
target,
reading_user,
&User.blocks_user?(&1, &2)
),
muting:
UserRelationship.exists?(
user_relationships,
:mute,
reading_user,
target,
&User.mutes?(&1, &2)
),
muting_notifications: muting_notifications:
test_rel( UserRelationship.exists?(
user_relationships, user_relationships,
:notification_mute, :notification_mute,
reading_user, reading_user,
@ -75,7 +109,7 @@ def render(
&User.muted_notifications?(&1, &2) &User.muted_notifications?(&1, &2)
), ),
subscribing: subscribing:
test_rel( UserRelationship.exists?(
user_relationships, user_relationships,
:inverse_subscription, :inverse_subscription,
target, target,
@ -85,7 +119,7 @@ def render(
requested: follow_state == "pending", requested: follow_state == "pending",
domain_blocking: User.blocks_domain?(reading_user, target), domain_blocking: User.blocks_domain?(reading_user, target),
showing_reblogs: showing_reblogs:
not test_rel( not UserRelationship.exists?(
user_relationships, user_relationships,
:reblog_mute, :reblog_mute,
reading_user, reading_user,
@ -139,7 +173,8 @@ defp do_render("show.json", %{user: user} = opts) do
render("relationship.json", %{ render("relationship.json", %{
user: opts[:for], user: opts[:for],
target: user, target: user,
user_relationships: opts[:user_relationships] user_relationships: opts[:user_relationships],
following_relationships: opts[:following_relationships]
}) })
%{ %{

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.ActivityExpiration alias Pleroma.ActivityExpiration
alias Pleroma.FollowingRelationship
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
@ -71,22 +72,31 @@ defp reblogged?(activity, user) do
present?(user && user.ap_id in (object.data["announcements"] || [])) present?(user && user.ap_id in (object.data["announcements"] || []))
end end
defp user_relationships_opt(opts) do defp relationships_opts(opts) do
reading_user = opts[:for] reading_user = opts[:for]
if reading_user do {user_relationships, following_relationships} =
activities = opts[:activities] if reading_user do
actors = Enum.map(activities, fn a -> get_user(a.data["actor"]) end) activities = opts[:activities]
actors = Enum.map(activities, fn a -> get_user(a.data["actor"]) end)
UserRelationship.dictionary( user_relationships =
[reading_user], UserRelationship.dictionary(
actors, [reading_user],
[:block, :mute, :notification_mute, :reblog_mute], actors,
[:block, :inverse_subscription] [:block, :mute, :notification_mute, :reblog_mute],
) [:block, :inverse_subscription]
else )
[]
end following_relationships =
FollowingRelationship.all_between_user_sets([reading_user], actors)
{user_relationships, following_relationships}
else
{[], []}
end
%{user_relationships: user_relationships, following_relationships: following_relationships}
end end
def render("index.json", opts) do def render("index.json", opts) do
@ -96,7 +106,7 @@ def render("index.json", opts) do
opts = opts =
opts opts
|> Map.put(:replied_to_activities, replied_to_activities) |> Map.put(:replied_to_activities, replied_to_activities)
|> Map.put(:user_relationships, user_relationships_opt(opts)) |> Map.merge(relationships_opts(opts))
safe_render_many(activities, StatusView, "show.json", opts) safe_render_many(activities, StatusView, "show.json", opts)
end end
@ -135,7 +145,8 @@ def render(
AccountView.render("show.json", %{ AccountView.render("show.json", %{
user: user, user: user,
for: opts[:for], for: opts[:for],
user_relationships: opts[:user_relationships] user_relationships: opts[:user_relationships],
following_relationships: opts[:following_relationships]
}), }),
in_reply_to_id: nil, in_reply_to_id: nil,
in_reply_to_account_id: nil, in_reply_to_account_id: nil,
@ -286,7 +297,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
muted = muted =
thread_muted? || thread_muted? ||
Pleroma.Web.MastodonAPI.AccountView.test_rel( UserRelationship.exists?(
user_relationships_opt, user_relationships_opt,
:mute, :mute,
opts[:for], opts[:for],
@ -302,7 +313,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
AccountView.render("show.json", %{ AccountView.render("show.json", %{
user: user, user: user,
for: opts[:for], for: opts[:for],
user_relationships: user_relationships_opt user_relationships: user_relationships_opt,
following_relationships: opts[:following_relationships]
}), }),
in_reply_to_id: reply_to && to_string(reply_to.id), in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),