From 3c78e5f3275494b3dc4546e65f19eb3a3c97033a Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 23 Mar 2020 12:01:11 +0300 Subject: [PATCH] Preloading of follow relations for timeline/statuses rendering (performance improvement). Refactoring. --- lib/pleroma/following_relationship.ex | 26 +++++++ lib/pleroma/user.ex | 7 ++ lib/pleroma/user_relationship.ex | 13 ++++ .../web/mastodon_api/views/account_view.ex | 75 ++++++++++++++----- .../web/mastodon_api/views/status_view.ex | 46 +++++++----- 5 files changed, 130 insertions(+), 37 deletions(-) diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index a6d281151..dd1696136 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -129,4 +129,30 @@ def move_following(origin, target) do move_following(origin, target) 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 diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index daaa6d86b..eb72755a0 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -674,7 +674,14 @@ def unfollow(%User{} = follower, %User{} = followed) do def get_follow_state(%User{} = follower, %User{} = following) do 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 {nil, false} -> case Utils.fetch_latest_follow(follower, following) do diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 167a3919c..9423e3a42 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -116,6 +116,19 @@ def dictionary( |> Repo.all() 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 changeset |> validate_change(:target_id, fn _, target_id -> diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 15a579278..2fe46158b 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -6,21 +6,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do use Pleroma.Web, :view alias Pleroma.User + alias Pleroma.UserRelationship alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MediaProxy - def test_rel(user_relationships, rel_type, source, target, func) do - cond do - is_nil(source) or is_nil(target) -> - false - - user_relationships -> - [rel_type, source.id, target.id] in user_relationships - - true -> - func.(source, target) - end + defp find_following_rel(following_relationships, follower, following) do + Enum.find(following_relationships, fn + fr -> fr.follower_id == follower.id and fr.following_id == following.id + end) end def render("index.json", %{users: users} = opts) do @@ -53,21 +47,61 @@ def render( %{user: %User{} = reading_user, target: %User{} = target} = opts ) do 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 %{ id: to_string(target.id), following: follow_state == "accept", - followed_by: User.following?(target, reading_user), + followed_by: followed_by, 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: - test_rel(user_relationships, :block, target, reading_user, &User.blocks_user?(&1, &2)), - muting: test_rel(user_relationships, :mute, reading_user, target, &User.mutes?(&1, &2)), + UserRelationship.exists?( + 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: - test_rel( + UserRelationship.exists?( user_relationships, :notification_mute, reading_user, @@ -75,7 +109,7 @@ def render( &User.muted_notifications?(&1, &2) ), subscribing: - test_rel( + UserRelationship.exists?( user_relationships, :inverse_subscription, target, @@ -85,7 +119,7 @@ def render( requested: follow_state == "pending", domain_blocking: User.blocks_domain?(reading_user, target), showing_reblogs: - not test_rel( + not UserRelationship.exists?( user_relationships, :reblog_mute, reading_user, @@ -139,7 +173,8 @@ defp do_render("show.json", %{user: user} = opts) do render("relationship.json", %{ user: opts[:for], target: user, - user_relationships: opts[:user_relationships] + user_relationships: opts[:user_relationships], + following_relationships: opts[:following_relationships] }) %{ diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index e0c368ec9..55a5513f9 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do alias Pleroma.Activity alias Pleroma.ActivityExpiration + alias Pleroma.FollowingRelationship alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Repo @@ -71,22 +72,31 @@ defp reblogged?(activity, user) do present?(user && user.ap_id in (object.data["announcements"] || [])) end - defp user_relationships_opt(opts) do + defp relationships_opts(opts) do reading_user = opts[:for] - if reading_user do - activities = opts[:activities] - actors = Enum.map(activities, fn a -> get_user(a.data["actor"]) end) + {user_relationships, following_relationships} = + if reading_user do + activities = opts[:activities] + actors = Enum.map(activities, fn a -> get_user(a.data["actor"]) end) - UserRelationship.dictionary( - [reading_user], - actors, - [:block, :mute, :notification_mute, :reblog_mute], - [:block, :inverse_subscription] - ) - else - [] - end + user_relationships = + UserRelationship.dictionary( + [reading_user], + actors, + [:block, :mute, :notification_mute, :reblog_mute], + [:block, :inverse_subscription] + ) + + 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 def render("index.json", opts) do @@ -96,7 +106,7 @@ def render("index.json", opts) do opts = opts |> 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) end @@ -135,7 +145,8 @@ def render( AccountView.render("show.json", %{ user: user, 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_account_id: nil, @@ -286,7 +297,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} muted = thread_muted? || - Pleroma.Web.MastodonAPI.AccountView.test_rel( + UserRelationship.exists?( user_relationships_opt, :mute, opts[:for], @@ -302,7 +313,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} AccountView.render("show.json", %{ user: user, 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_account_id: reply_to_user && to_string(reply_to_user.id),