Moves suggestion logic to celery

This commit is contained in:
Mouse Reeve 2021-05-22 15:53:07 -07:00
parent 644e5926db
commit edfc27a3cd
3 changed files with 49 additions and 17 deletions

View file

@ -50,7 +50,7 @@ class RedisStore(ABC):
pipeline.execute() pipeline.execute()
def bulk_remove_objects_from_store(self, objs, store): def bulk_remove_objects_from_store(self, objs, store):
"""remoev a list of objects from a given store""" """remove a list of objects from a given store"""
pipeline = r.pipeline() pipeline = r.pipeline()
for obj in objs[: self.max_length]: for obj in objs[: self.max_length]:
pipeline.zrem(store, -1, obj.id) pipeline.zrem(store, -1, obj.id)

View file

@ -5,6 +5,7 @@ from django.db.models import signals, Count, Q
from bookwyrm import models from bookwyrm import models
from bookwyrm.redis_store import RedisStore, r from bookwyrm.redis_store import RedisStore, r
from bookwyrm.tasks import app
class SuggestedUsers(RedisStore): class SuggestedUsers(RedisStore):
@ -18,6 +19,8 @@ class SuggestedUsers(RedisStore):
def store_id(self, user): # pylint: disable=no-self-use def store_id(self, user): # pylint: disable=no-self-use
"""the key used to store this user's recs""" """the key used to store this user's recs"""
if isinstance(user, int):
return "{:d}-suggestions".format(user)
return "{:d}-suggestions".format(user.id) return "{:d}-suggestions".format(user.id)
def get_counts_from_rank(self, rank): # pylint: disable=no-self-use def get_counts_from_rank(self, rank): # pylint: disable=no-self-use
@ -46,7 +49,9 @@ class SuggestedUsers(RedisStore):
"""given a user, who might want to follow them""" """given a user, who might want to follow them"""
return models.User.objects.filter( return models.User.objects.filter(
local=True, local=True,
).exclude(following=obj) ).exclude(
Q(id=obj.id) | Q(followers=obj) | Q(id__in=obj.blocks.all()) | Q(blocks=obj)
)
def rerank_obj(self, obj, update_only=True): def rerank_obj(self, obj, update_only=True):
"""update all the instances of this user with new ranks""" """update all the instances of this user with new ranks"""
@ -56,6 +61,8 @@ class SuggestedUsers(RedisStore):
store_user, store_user,
id=obj.id, id=obj.id,
).first() ).first()
if not annotated_user:
continue
pipeline.zadd( pipeline.zadd(
self.store_id(store_user), self.store_id(store_user),
@ -66,8 +73,6 @@ class SuggestedUsers(RedisStore):
def rerank_user_suggestions(self, user): def rerank_user_suggestions(self, user):
"""update the ranks of the follows suggested to a user""" """update the ranks of the follows suggested to a user"""
if not user.local:
raise ValueError("Trying to create suggestions for remote user: ", user.id)
self.populate_store(self.store_id(user)) self.populate_store(self.store_id(user))
def remove_suggestion(self, user, suggested_user): def remove_suggestion(self, user, suggested_user):
@ -128,8 +133,8 @@ def update_suggestions_on_follow(sender, instance, created, *args, **kwargs):
return return
if instance.user_subject.local: if instance.user_subject.local:
suggested_users.remove_suggestion(instance.user_subject, instance.user_object) remove_suggestion_task.delay(instance.user_subject.id, instance.user_object.id)
suggested_users.rerank_obj(instance.user_object) rerank_user_task.delay(instance.user_object.id)
@receiver(signals.post_save, sender=models.UserBlocks) @receiver(signals.post_save, sender=models.UserBlocks)
@ -137,9 +142,9 @@ def update_suggestions_on_follow(sender, instance, created, *args, **kwargs):
def update_suggestions_on_block(sender, instance, *args, **kwargs): def update_suggestions_on_block(sender, instance, *args, **kwargs):
"""remove blocked users from recs""" """remove blocked users from recs"""
if instance.user_subject.local: if instance.user_subject.local:
suggested_users.remove_suggestion(instance.user_subject, instance.user_object) remove_suggestion_task.delay(instance.user_subject.id, instance.user_object.id)
if instance.user_object.local: if instance.user_object.local:
suggested_users.remove_suggestion(instance.user_object, instance.user_subject) remove_suggestion_task.delay(instance.user_object.id, instance.user_subject.id)
@receiver(signals.post_delete, sender=models.UserFollows) @receiver(signals.post_delete, sender=models.UserFollows)
@ -147,7 +152,7 @@ def update_suggestions_on_block(sender, instance, *args, **kwargs):
def update_suggestions_on_unfollow(sender, instance, **kwargs): def update_suggestions_on_unfollow(sender, instance, **kwargs):
"""update rankings, but don't re-suggest because it was probably intentional""" """update rankings, but don't re-suggest because it was probably intentional"""
if instance.user_object.discoverable: if instance.user_object.discoverable:
suggested_users.rerank_obj(instance.user_object) rerank_user_task.delay(instance.user_object.id)
@receiver(signals.post_save, sender=models.ShelfBook) @receiver(signals.post_save, sender=models.ShelfBook)
@ -157,24 +162,50 @@ def update_rank_on_shelving(sender, instance, *args, **kwargs):
"""when a user shelves or unshelves a book, re-compute their rank""" """when a user shelves or unshelves a book, re-compute their rank"""
# if it's a local user, re-calculate who is rec'ed to them # if it's a local user, re-calculate who is rec'ed to them
if instance.user.local: if instance.user.local:
suggested_users.rerank_user_suggestions(instance.user) rerank_suggestions_task.delay(instance.user.id)
# if the user is discoverable, update their rankings # if the user is discoverable, update their rankings
if not instance.user.discoverable: if instance.user.discoverable:
return rerank_user_task.delay(instance.user.id)
suggested_users.rerank_obj(instance.user)
@receiver(signals.post_save, sender=models.User) @receiver(signals.post_save, sender=models.User)
# pylint: disable=unused-argument, too-many-arguments # pylint: disable=unused-argument, too-many-arguments
def add_new_user(sender, instance, created, **kwargs): def add_new_user(sender, instance, created, **kwargs):
"""a new user, wow how cool""" """a new user, wow how cool"""
# a new user is found, create suggestions for them
if created and instance.local: if created and instance.local:
# a new user is found, create suggestions for them rerank_suggestions_task.delay(instance.id)
suggested_users.rerank_user_suggestions(instance)
# this happens on every save, not just when discoverability changes, annoyingly # this happens on every save, not just when discoverability changes, annoyingly
if instance.discoverable: if instance.discoverable:
suggested_users.rerank_obj(instance, update_only=False) rerank_user_task.delay(instance.id, update_only=False)
elif not created: elif not created:
suggested_users.remove_object_from_related_stores(instance) remove_user_task.delay(instance.id)
@app.task
def rerank_suggestions_task(user_id):
"""do the hard work in celery"""
suggested_users.rerank_user_suggestions(user_id)
@app.task
def rerank_user_task(user_id, update_only=False):
"""do the hard work in celery"""
user = models.User.objects.get(id=user_id)
suggested_users.rerank_obj(user, update_only=update_only)
@app.task
def remove_user_task(user_id):
"""do the hard work in celery"""
user = models.User.objects.get(id=user_id)
suggested_users.remove_object_from_related_stores(user)
@app.task
def remove_suggestion_task(user_id, suggested_user_id):
"""remove a specific user from a specific user's suggestions"""
suggested_user = models.User.objects.get(id=suggested_user_id)
suggested_users.remove_suggestion(user_id, suggested_user)

View file

@ -25,4 +25,5 @@ app.autodiscover_tasks(["bookwyrm"], related_name="connectors.abstract_connector
app.autodiscover_tasks(["bookwyrm"], related_name="emailing") app.autodiscover_tasks(["bookwyrm"], related_name="emailing")
app.autodiscover_tasks(["bookwyrm"], related_name="goodreads_import") app.autodiscover_tasks(["bookwyrm"], related_name="goodreads_import")
app.autodiscover_tasks(["bookwyrm"], related_name="models.user") app.autodiscover_tasks(["bookwyrm"], related_name="models.user")
app.autodiscover_tasks(["bookwyrm"], related_name="suggested_users")
app.autodiscover_tasks(["bookwyrm"], related_name="views.inbox") app.autodiscover_tasks(["bookwyrm"], related_name="views.inbox")