Remove subscriptions using bulk queries.

This improves the performance of unsubscribing to N streams by more
than a factor of 10 for large N.

(imported from commit a529e6d3ac4452f49c2294908d275280019bbd05)
This commit is contained in:
Tim Abbott 2013-06-28 11:16:55 -04:00
parent 7f3fded612
commit 74fd508b2f
2 changed files with 51 additions and 17 deletions

View File

@ -635,6 +635,50 @@ def do_add_subscription(user_profile, stream, no_log=False):
notify_new_subscription(user_profile, stream, subscription, no_log)
return did_subscribe
def notify_subscription_removed(user_profile, stream, no_log=False):
if not no_log:
log_event({'type': 'subscription_removed',
'user': user_profile.email,
'name': stream.name,
'domain': stream.realm.domain})
notice = dict(event=dict(type="subscription", op="remove",
subscription=dict(name=stream.name)),
users=[user_profile.id])
tornado_callbacks.send_notification(notice)
def bulk_remove_subscriptions(users, streams):
recipients_map = bulk_get_recipients(Recipient.STREAM,
[stream.id for stream in streams])
stream_map = {}
for stream in streams:
stream_map[recipients_map[stream.id].id] = stream
subs_by_user = dict((user_profile.id, []) for user_profile in users)
for sub in Subscription.objects.select_related("user_profile").filter(user_profile__in=users,
recipient__in=recipients_map.values(),
active=True):
subs_by_user[sub.user_profile_id].append(sub)
subs_to_deactivate = []
not_subscribed = []
for user_profile in users:
recipients_to_unsub = set([recipient.id for recipient in recipients_map.values()])
for sub in subs_by_user[user_profile.id]:
recipients_to_unsub.remove(sub.recipient_id)
subs_to_deactivate.append((sub, stream_map[sub.recipient_id]))
for recipient_id in recipients_to_unsub:
not_subscribed.append((user_profile, stream_map[recipient_id]))
Subscription.objects.filter(id__in=[sub.id for (sub, stream_name) in
subs_to_deactivate]).update(active=False)
for (sub, stream) in subs_to_deactivate:
notify_subscription_removed(sub.user_profile, stream)
return ([(sub.user_profile, stream) for (sub, stream) in subs_to_deactivate],
not_subscribed)
def do_remove_subscription(user_profile, stream, no_log=False):
recipient = get_recipient(Recipient.STREAM, stream.id)
maybe_sub = Subscription.objects.filter(user_profile=user_profile,
@ -646,16 +690,7 @@ def do_remove_subscription(user_profile, stream, no_log=False):
subscription.active = False
subscription.save(update_fields=["active"])
if did_remove:
if not no_log:
log_event({'type': 'subscription_removed',
'user': user_profile.email,
'name': stream.name,
'domain': stream.realm.domain})
notice = dict(event=dict(type="subscription", op="remove",
subscription=dict(name=stream.name)),
users=[user_profile.id])
tornado_callbacks.send_notification(notice)
notify_subscription_removed(user_profile, stream, no_log)
return did_remove

View File

@ -21,7 +21,7 @@ from zephyr.models import Message, UserProfile, Stream, Subscription, \
MAX_SUBJECT_LENGTH, get_stream, bulk_get_streams, UserPresence, \
get_recipient, valid_stream_name, to_dict_cache_key, to_dict_cache_key_id, \
extract_message_dict, stringify_message_dict, parse_usermessage_flags
from zephyr.lib.actions import do_remove_subscription, \
from zephyr.lib.actions import do_remove_subscription, bulk_remove_subscriptions, \
do_change_password, create_mit_user_if_needed, do_change_full_name, \
do_change_enable_desktop_notifications, do_change_enter_sends, do_change_enable_sounds, \
do_send_confirmation_email, do_activate_user, do_create_user, check_send_message, \
@ -1240,12 +1240,11 @@ def remove_subscriptions_backend(request, user_profile,
streams = list_to_streams(streams_raw, user_profile)
result = dict(removed=[], not_subscribed=[])
for stream in streams:
did_remove = do_remove_subscription(user_profile, stream)
if did_remove:
result["removed"].append(stream.name)
else:
result["not_subscribed"].append(stream.name)
(removed, not_subscribed) = bulk_remove_subscriptions([user_profile], streams)
for (subscriber, stream) in removed:
result["removed"].append(stream.name)
for (subscriber, stream) in not_subscribed:
result["not_subscribed"].append(stream.name)
return json_success(result)