migrations: Fix migration to set default for can_mention_group.

This commit updates `0455_set_default_for_can_mention_group`
migration to be more efficient when running for a large number
of UserGroup objects.

Previously, we did a loop over all UserGroup objects and
then did a `bulk_update`. All this happened in a single
transaction and the transaction was being hold for
unacceptably long time for a server with large number
of user groups. Also the SQL generated by Django for
`bulk_update` took almost quadratic time to evaluate,
as the SQL had linear length "CASE" statement which was
being resolved for each row.

We instead now use ".update" so that we can write the migration
without using loop and update the objects in batches of size
1000 so that we do not hold a transaction for very long time.
This also helps in avoiding the inefficient SQL that was being
executed due to using `bulk_update`.

We also update the queries to exclude the groups that already
have `can_mention_group` set to a non-null value, as this will
help in migration completing quickly when running it more than
once.
This commit is contained in:
Sahil Batra 2023-07-21 12:13:04 +05:30 committed by Tim Abbott
parent 02c279c6b6
commit 1d3f5a0368
1 changed files with 33 additions and 18 deletions

View File

@ -1,36 +1,51 @@
# Generated by Django 4.2.1 on 2023-06-12 10:47
from django.db import migrations
from django.db import migrations, transaction
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps
from django.db.models import Max, Min, OuterRef
def set_default_value_for_can_mention_group(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
Realm = apps.get_model("zerver", "Realm")
UserGroup = apps.get_model("zerver", "UserGroup")
groups_to_update = []
for realm in Realm.objects.all():
# The default value of `can_mention_group` is everyone group for
# all groups except role-based system groups. For role-based system
# groups, we set the value of `can_mention_group` to nobody group.
nobody_group = UserGroup.objects.get(name="@role:nobody", realm=realm, is_system_group=True)
everyone_group = UserGroup.objects.get(
name="@role:everyone", realm=realm, is_system_group=True
)
for group in UserGroup.objects.filter(realm=realm):
if group.is_system_group:
group.can_mention_group = nobody_group
else:
group.can_mention_group = everyone_group
groups_to_update.append(group)
BATCH_SIZE = 1000
max_id = UserGroup.objects.filter(can_mention_group=None).aggregate(Max("id"))["id__max"]
UserGroup.objects.bulk_update(groups_to_update, ["can_mention_group"])
if max_id is None:
# Do nothing if there are no UserGroups on the server.
return
lower_bound = UserGroup.objects.filter(can_mention_group=None).aggregate(Min("id"))["id__min"]
while lower_bound <= max_id:
upper_bound = lower_bound + BATCH_SIZE - 1
print(f"Processing batch {lower_bound} to {upper_bound} for UserGroup")
with transaction.atomic():
UserGroup.objects.filter(
id__range=(lower_bound, upper_bound), can_mention_group=None, is_system_group=True
).update(
can_mention_group=UserGroup.objects.filter(
name="@role:nobody", realm=OuterRef("realm"), is_system_group=True
).values("pk")
)
UserGroup.objects.filter(
id__range=(lower_bound, upper_bound), can_mention_group=None, is_system_group=False
).update(
can_mention_group=UserGroup.objects.filter(
name="@role:everyone", realm=OuterRef("realm"), is_system_group=True
).values("pk")
)
lower_bound += BATCH_SIZE
class Migration(migrations.Migration):
atomic = False
dependencies = [
("zerver", "0454_usergroup_can_mention_group"),
]