Skip to content

Commit d0060f4

Browse files
committed
Delete non fxa-migrated users w/o content
1 parent e18e5b6 commit d0060f4

File tree

1 file changed

+93
-28
lines changed

1 file changed

+93
-28
lines changed
Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,103 @@
1+
import time
2+
from datetime import timedelta
3+
from itertools import islice
4+
15
from django.db import migrations
26
from django.db.models import Q
37

48

59
def delete_non_migrated_users(apps, schema_editor):
610
"""
711
Delete users where is_fxa_migrated is False and who aren't creators/owners/users of
8-
any important content
12+
any content.
913
"""
10-
User = apps.get_model('auth', 'User')
11-
12-
users_to_delete = User.objects.filter(
13-
profile__is_fxa_migrated=False
14-
).exclude(
15-
Q(answer_votes__isnull=False) |
16-
Q(answers__isnull=False) |
17-
Q(award_creator__isnull=False) |
18-
Q(badge__isnull=False) |
19-
Q(created_revisions__isnull=False) |
20-
Q(gallery_images__isnull=False) |
21-
Q(gallery_videos__isnull=False) |
22-
Q(inboxmessage__isnull=False) |
23-
Q(outbox__isnull=False) |
24-
Q(poll_votes__isnull=False) |
25-
Q(post__isnull=False) |
26-
Q(question_votes__isnull=False) |
27-
Q(questions__isnull=False) |
28-
Q(readied_for_l10n_revisions__isnull=False) |
29-
Q(reviewed_revisions__isnull=False) |
30-
Q(thread__isnull=False) |
31-
Q(wiki_post_set__isnull=False) |
32-
Q(wiki_thread_set__isnull=False)
14+
User = apps.get_model("auth", "User")
15+
users_to_delete = User.objects.filter(profile__is_fxa_migrated=False).exclude(
16+
Q(answer_votes__isnull=False)
17+
| Q(answers__isnull=False)
18+
| Q(award_creator__isnull=False)
19+
| Q(badge__isnull=False)
20+
| Q(created_revisions__isnull=False)
21+
| Q(gallery_images__isnull=False)
22+
| Q(gallery_videos__isnull=False)
23+
| Q(inboxmessage__isnull=False)
24+
| Q(outbox__isnull=False)
25+
| Q(poll_votes__isnull=False)
26+
| Q(post__isnull=False)
27+
| Q(question_votes__isnull=False)
28+
| Q(questions__isnull=False)
29+
| Q(readied_for_l10n_revisions__isnull=False)
30+
| Q(reviewed_revisions__isnull=False)
31+
| Q(thread__isnull=False)
32+
| Q(wiki_post_set__isnull=False)
33+
| Q(wiki_thread_set__isnull=False)
34+
| Q(locales_leader__isnull=False)
35+
| Q(locales_reviewer__isnull=False)
36+
| Q(locales_editor__isnull=False)
37+
| Q(wiki_contributions__isnull=False)
38+
)
39+
40+
total_users = users_to_delete.count()
41+
if total_users == 0:
42+
print("No users to delete")
43+
return
44+
45+
print(f"Starting deletion of {total_users:,} users")
46+
start_time = time.time()
47+
deleted_count = 0
48+
batch_size = 2000
49+
50+
user_ids = users_to_delete.values_list("id", flat=True).iterator(chunk_size=batch_size)
51+
current_batch = []
52+
53+
for user_id in user_ids:
54+
current_batch.append(user_id)
55+
56+
if len(current_batch) >= batch_size:
57+
# Delete the batch using _base_manager to avoid the overridden managers
58+
# of each model through the cascade.
59+
# We don't care about the extra logic b/c the accounts that are being deleted are empty
60+
deletion_counts = User._base_manager.filter(id__in=current_batch).delete()
61+
# get the user, not the cascaded deletes
62+
user_deletes = deletion_counts[1].get("auth.User", 0)
63+
deleted_count += user_deletes
64+
current_batch = []
65+
66+
elapsed_time = time.time() - start_time
67+
avg_time_per_user = elapsed_time / deleted_count if deleted_count > 0 else 0
68+
current_rate = deleted_count / elapsed_time * 60 if elapsed_time > 0 else 0
69+
remaining_time = (
70+
(total_users - deleted_count) * avg_time_per_user if deleted_count > 0 else 0
71+
)
72+
73+
print(
74+
f"""
75+
Progress Report:
76+
---------------
77+
Users Deleted: {deleted_count:,} of {total_users:,} ({(deleted_count/total_users*100):.1f}%)
78+
Elapsed Time: {timedelta(seconds=int(elapsed_time))}
79+
Average Time per User: {avg_time_per_user:.3f} seconds
80+
Current Rate: {current_rate:.1f} users/minute
81+
Estimated Time Remaining: {timedelta(seconds=int(remaining_time))}
82+
"""
83+
)
84+
85+
if current_batch:
86+
deletion_counts = User._base_manager.filter(id__in=current_batch).delete()
87+
user_deletes = deletion_counts[1].get("auth.User", 0)
88+
deleted_count += user_deletes
89+
90+
total_time = time.time() - start_time
91+
print(
92+
f"""
93+
Deletion Complete:
94+
-----------------
95+
Total Users Deleted: {deleted_count:,} of {total_users:,}
96+
Total Time: {timedelta(seconds=int(total_time))}
97+
Average Time per User: {(total_time/deleted_count if deleted_count > 0 else 0):.3f} seconds
98+
Overall Rate: {(deleted_count/total_time*60 if total_time > 0 else 0):.1f} users/minute
99+
"""
33100
)
34-
35-
users_to_delete.delete()
36101

37102

38103
def reverse_migration(apps, schema_editor):
@@ -44,12 +109,12 @@ def reverse_migration(apps, schema_editor):
44109

45110
class Migration(migrations.Migration):
46111
dependencies = [
47-
('users', '0032_profile_account_type_alter_profile_user'),
112+
("users", "0032_profile_account_type_alter_profile_user"),
48113
]
49114

50115
operations = [
51116
migrations.RunPython(
52117
delete_non_migrated_users,
53118
reverse_migration,
54119
),
55-
]
120+
]

0 commit comments

Comments
 (0)