diff --git a/backend/coreapp/housekeeping.py b/backend/coreapp/housekeeping.py index ec1a882ae..b29c1b241 100644 --- a/backend/coreapp/housekeeping.py +++ b/backend/coreapp/housekeeping.py @@ -13,11 +13,25 @@ def get_model(model_name: str) -> type[DjangoModel]: return apps.get_model("coreapp", model_name.capitalize()) -def perform_delete(qs: QuerySet[Model], dry_run: bool = False) -> int: +DEFAULT_DELETE_BATCH_SIZE = 1000 + + +def perform_delete( + qs: QuerySet[Model], + dry_run: bool = False, + batch_size: int | None = DEFAULT_DELETE_BATCH_SIZE, +) -> int: count = qs.count() if dry_run: return count + if batch_size is not None: + deleted = 0 + while ids := list(qs.values_list("pk", flat=True)[:batch_size]): + batch_deleted, _ = qs.filter(pk__in=ids).delete() + deleted += batch_deleted + return deleted + deleted, _ = qs.delete() return deleted diff --git a/backend/coreapp/tests/test_housekeeping.py b/backend/coreapp/tests/test_housekeeping.py index 8f595b4cf..794bef533 100644 --- a/backend/coreapp/tests/test_housekeeping.py +++ b/backend/coreapp/tests/test_housekeeping.py @@ -4,6 +4,7 @@ from django.test import TestCase from coreapp.housekeeping import ( + perform_delete, remove_anonymous_profiles, remove_orphan_asms, remove_orphan_assemblies, @@ -99,6 +100,19 @@ def test_dry_run_counts_ownerless_scratches_without_deleting(self) -> None: self.assertEqual(deleted, 1) self.assertTrue(Scratch.objects.filter(pk=old_ownerless_scratch.pk).exists()) + def test_perform_delete_can_delete_in_batches(self) -> None: + asms = [ + Asm.objects.create(hash=f"batched-asm-{index}", data="jr $ra\nnop") + for index in range(3) + ] + + deleted = perform_delete( + Asm.objects.filter(pk__in=[asm.pk for asm in asms]), batch_size=1 + ) + + self.assertEqual(deleted, 3) + self.assertFalse(Asm.objects.filter(pk__in=[asm.pk for asm in asms]).exists()) + def test_removes_scratchless_anonymous_profiles_created_before_cutoff(self) -> None: old_scratchless_profile = Profile.objects.create(user=None) old_profile_with_scratch = Profile.objects.create(user=None)