Optimize get_audience

This avoids filtering for the user that made the post in the same query
as we use for other things, which should allow for better use of indices
in all cases. Previously, #2723 did some work on this that only worked
for some cases in HomeStream, but this code should work for all cases.

Related: #2720
This commit is contained in:
Wesley Aptekar-Cassels 2023-04-06 00:35:15 -04:00
parent 77264493eb
commit 07b50a1453

View file

@ -107,7 +107,7 @@ class ActivityStream(RedisStore):
@tracer.start_as_current_span("ActivityStream._get_audience") @tracer.start_as_current_span("ActivityStream._get_audience")
def _get_audience(self, status): # pylint: disable=no-self-use def _get_audience(self, status): # pylint: disable=no-self-use
"""given a status, what users should see it""" """given a status, what users should see it, excluding the author"""
trace.get_current_span().set_attribute("status_type", status.status_type) trace.get_current_span().set_attribute("status_type", status.status_type)
trace.get_current_span().set_attribute("status_privacy", status.privacy) trace.get_current_span().set_attribute("status_privacy", status.privacy)
trace.get_current_span().set_attribute( trace.get_current_span().set_attribute(
@ -129,15 +129,13 @@ class ActivityStream(RedisStore):
# only visible to the poster and mentioned users # only visible to the poster and mentioned users
if status.privacy == "direct": if status.privacy == "direct":
audience = audience.filter( audience = audience.filter(
Q(id=status.user.id) # if the user is the post's author Q(id__in=status.mention_users.all()) # if the user is mentioned
| Q(id__in=status.mention_users.all()) # if the user is mentioned
) )
# don't show replies to statuses the user can't see # don't show replies to statuses the user can't see
elif status.reply_parent and status.reply_parent.privacy == "followers": elif status.reply_parent and status.reply_parent.privacy == "followers":
audience = audience.filter( audience = audience.filter(
Q(id=status.user.id) # if the user is the post's author Q(id=status.reply_parent.user.id) # if the user is the OG author
| Q(id=status.reply_parent.user.id) # if the user is the OG author
| ( | (
Q(following=status.user) & Q(following=status.reply_parent.user) Q(following=status.user) & Q(following=status.reply_parent.user)
) # if the user is following both authors ) # if the user is following both authors
@ -146,8 +144,7 @@ class ActivityStream(RedisStore):
# only visible to the poster's followers and tagged users # only visible to the poster's followers and tagged users
elif status.privacy == "followers": elif status.privacy == "followers":
audience = audience.filter( audience = audience.filter(
Q(id=status.user.id) # if the user is the post's author Q(following=status.user) # if the user is following the author
| Q(following=status.user) # if the user is following the author
) )
return audience.distinct() return audience.distinct()
@ -155,7 +152,11 @@ class ActivityStream(RedisStore):
def get_audience(self, status): def get_audience(self, status):
"""given a status, what users should see it""" """given a status, what users should see it"""
trace.get_current_span().set_attribute("stream_id", self.key) trace.get_current_span().set_attribute("stream_id", self.key)
return [user.id for user in self._get_audience(status)] audience = self._get_audience(status)
status_author = models.User.objects.filter(
is_active=True, local=True, id=status.user.id
)
return list({user.id for user in list(audience) + list(status_author)})
def get_stores_for_users(self, user_ids): def get_stores_for_users(self, user_ids):
"""convert a list of user ids into redis store ids""" """convert a list of user ids into redis store ids"""
@ -184,11 +185,13 @@ class HomeStream(ActivityStream):
audience = super()._get_audience(status) audience = super()._get_audience(status)
if not audience: if not audience:
return [] return []
# if the user is the post's author
ids_self = [user.id for user in audience.filter(Q(id=status.user.id))]
# if the user is following the author # if the user is following the author
ids_following = [user.id for user in audience.filter(Q(following=status.user))] audience = audience.filter(following=status.user)
return ids_self + ids_following # if the user is the post's author
status_author = models.User.objects.filter(
is_active=True, local=True, id=status.user.id
)
return list({user.id for user in list(audience) + list(status_author)})
def get_statuses_for_user(self, user): def get_statuses_for_user(self, user):
return models.Status.privacy_filter( return models.Status.privacy_filter(
@ -229,13 +232,6 @@ class BooksStream(ActivityStream):
def _get_audience(self, status): def _get_audience(self, status):
"""anyone with the mentioned book on their shelves""" """anyone with the mentioned book on their shelves"""
# only show public statuses on the books feed,
# and only statuses that mention books
if status.privacy != "public" or not (
status.mention_books.exists() or hasattr(status, "book")
):
return []
work = ( work = (
status.book.parent_work status.book.parent_work
if hasattr(status, "book") if hasattr(status, "book")
@ -247,6 +243,16 @@ class BooksStream(ActivityStream):
return [] return []
return audience.filter(shelfbook__book__parent_work=work).distinct() return audience.filter(shelfbook__book__parent_work=work).distinct()
def get_audience(self, status):
# only show public statuses on the books feed,
# and only statuses that mention books
if status.privacy != "public" or not (
status.mention_books.exists() or hasattr(status, "book")
):
return []
return super().get_audience(status)
def get_statuses_for_user(self, user): def get_statuses_for_user(self, user):
"""any public status that mentions the user's books""" """any public status that mentions the user's books"""
books = user.shelfbook_set.values_list( books = user.shelfbook_set.values_list(