Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

502 remediation #5963

Merged
merged 9 commits into from
Apr 19, 2024
Merged

502 remediation #5963

merged 9 commits into from
Apr 19, 2024

Conversation

smithellis
Copy link
Contributor

Optimize db queries for 502

In send_message in utils.py:

  • Reduce number of queries
  • Prefetch group members
  • One query for users_to_email

In MultiUsernameorGroupnameField in form_fields.py

  • Get users and groups in one query each
  • A single pass through to collect found_names

Refactor _add_recipients

  • Mainly readability
In `send_message` in `utils.py`:
* Reduce number of queries
*  Prefetch group members
*  One query for users_to_email

In `MultiUsernameorGroupnameField` in `form_fields.py`
* Get users and groups in one query each
* A single pass through to collect `found_names`
@smithellis smithellis force-pushed the 502-remediation branch 2 times, most recently from e600cc1 to fc930f3 Compare April 17, 2024 20:55
kitsune/messages/views.py Outdated Show resolved Hide resolved
kitsune/messages/views.py Outdated Show resolved Hide resolved
kitsune/messages/utils.py Outdated Show resolved Hide resolved
kitsune/messages/utils.py Outdated Show resolved Hide resolved
kitsune/messages/jinja2/messages/outbox.html Outdated Show resolved Hide resolved
inbox_messages = [
InboxMessage(sender=sender, to=user, message=text) for user in all_recipients_of_message
]
messages = InboxMessage.objects.bulk_create(inbox_messages)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the bulk_create() change! I noticed however that you're not setting the to_group field on each of the InboxMessage's like you used to. Are we getting rid of to_group on the InboxMessage? If not, I wonder if we could do a bulk_update() of the to_group field? That would be nice, but not sure if the bulk_update works with many-to-many fields.

kitsune/messages/utils.py Outdated Show resolved Hide resolved
@smithellis smithellis force-pushed the 502-remediation branch 3 times, most recently from a2981e5 to 82c249a Compare April 18, 2024 20:21
kitsune/messages/jinja2/messages/outbox.html Outdated Show resolved Hide resolved
outbox_message.to.set(users)
for obj in to:
if isinstance(obj, User):
usernames.append(obj)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
usernames.append(obj)
usernames.append(obj.username)
Copy link
Contributor

@escattone escattone Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, since we want the actual User objects later for setting the to field of the OutboxMessage, can't we just collect those here -- in addition to the usernames above -- by creating another list called users and doing a users.append(obj) here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is bad naming on behalf of a cut and paste - these are objects being collected, not names. Even my comment is broken here - I'll fix this up. But we get the objects separated here.

if isinstance(obj, User):
usernames.append(obj)
else:
group_names.append(obj)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
group_names.append(obj)
group_names.append(obj.name)
if isinstance(obj, User):
usernames.append(obj)
else:
group_names.append(obj)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we want the actual Group objects later for setting the to_group of the OutboxMessage, can't we just collect those here -- in addition to the group_names above -- by creating another list called groups and doing a groups.append(obj) here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - see note above. Bad naming...I'll fix.

else:
group_names.append(obj)

groups_qs = Group.objects.filter(name__in=group_names)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking we can delete this line, and use the groups list above instead.

Comment on lines 31 to 40
users_qs = User.objects.filter(username__in=usernames).values_list("id", flat=True)

# Group users' pks only - could be a lot of them
group_users_qs = User.objects.filter(groups__in=groups_qs).values_list("id", flat=True)

if groups:
# Add the groups from the To field to the message
outbox_message.to_group.set(groups)
# Resolve all unique user ids, including those in groups
# but keep up with users from groups
to_users = set(users_qs)
group_users = set(group_users_qs)
all_recipients_of_message = to_users.union(group_users)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking we could replace all of this with a single query:

all_recipients_of_message = set(User.objects.filter(Q(username__in=usernames) | Q(groups__name__in=group_names)).values_list("id", flat=True))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to keep up with the users that came from the to field and the users that came from the groups from the to field. So capturing the pks in separate sets will matter later. We don't want to set the to in an outbox message to every user; just to the users that were entered individually.

set_of_user_pks_to_email_private_message = set(
# Create the outbox message
outbox_message = OutboxMessage.objects.create(sender=sender, message=text)
to_users = User.objects.filter(id__in=to_users)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to_users since we'd already have the users list from my comments above.

kitsune/messages/utils.py Outdated Show resolved Hide resolved
Setting.objects.filter(
user__in=receivers, name="email_private_messages", value=True
).values_list("user__pk", flat=True)
user__in=to_users, name="email_private_messages", value=True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we want include all users, so the users from the groups as well, so I think this should be:

Suggested change
user__in=to_users, name="email_private_messages", value=True
user__in=all_recipients_of_message, name="email_private_messages", value=True
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are disabling e-mail (not messaging, just email) for users in Groups for now, pending some follow up.

Comment on lines 63 to 69
InboxMessage.objects.bulk_create(inbox_messages)

# Assuming messages are fetched again with IDs if necessary
for user_id in users_to_email:
inbox_messages = InboxMessage.objects.filter(sender=sender, to__id=user_id, message=text)
for message in inbox_messages:
email_private_message.delay(inbox_message_id=message.id)
Copy link
Contributor

@escattone escattone Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bulk_create returns the list of created InboxMessage's -- so they each have a valid id -- but we're not using that list.

Instead of querying for each InboxMessage for each of the users_to_email, which could be a large number of queries, over 2k for the Contributors group for example, it might be better to use that list of created InboxMessage's to create a dictionary that maps the to_id to the id of each of the messages, and then we can use that mapping to quickly get the message id for each of the users we need to email.

So I'm thinking of something like:

    # Create a mapping from the user id to the message id, so just integer to integer.
    recipient_id_to_message_id = {msg.to_id : msg.id for msg in InboxMessage.objects.bulk_create(inbox_messages)}

    # Use the mapping to get the message id for each user.
    for user_id in users_to_email:
        if (message_id := recipient_id_to_message_id.get(user_id)):
            email_private_message.delay(inbox_message_id=message_id)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was sketchy in the docs vs. info from other sources. Good to know!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented!

* Drop to_group form inboxmessage model
* Use `values_list` for large queries
* celery for email
Copy link
Contributor

@escattone escattone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r+wc

{%- else -%}
{{ user.profile.display_name }}
{%- endif -%}
{%- if message.recipients_count>1 -%}, ...{% endif %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Add spaces around >.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

{% set group_slug = profile.slug %}
<a href="{{ url('groups.profile', group_slug) }}">{{ group }}</a>
{%- endfor -%}
{% if message.to_groups_count>1 %}, ...{% endif %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Add spaces around >.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

Comment on lines 72 to 73
{% set group_slug = profile.slug %}
<a href="{{ url('groups.profile', group_slug) }}">{{ group }}</a>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit.

Suggested change
{% set group_slug = profile.slug %}
<a href="{{ url('groups.profile', group_slug) }}">{{ group }}</a>
<a href="{{ url('groups.profile', profile.slug) }}">{{ group }}</a>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Setting.objects.filter(
user__in=receivers, name="email_private_messages", value=True
).values_list("user__pk", flat=True)
user__in=users, name="email_private_messages", value=True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to record that I'd prefer this to use all_recipients_of_message instead of just the users, but that's for another discussion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

@smithellis smithellis marked this pull request as ready for review April 19, 2024 20:55
@smithellis smithellis merged commit dfac0c2 into mozilla:main Apr 19, 2024
2 checks passed
@smithellis smithellis deleted the 502-remediation branch April 19, 2024 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants