Skip to content

Commit acd80d1

Browse files
feat: add cronjob to delete unused hardware status rows every week
1 parent 1edb054 commit acd80d1

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

backend/kernelCI/settings.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,13 @@ def get_json_env_var(name, default):
179179
"--yes",
180180
],
181181
),
182+
(
183+
"0 0 * * 1",
184+
"django.core.management.call_command",
185+
[
186+
"delete_unused_hardware_status",
187+
],
188+
),
182189
]
183190

184191
# Email settings for SMTP backend
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""
2+
Management command to delete unused entries from hardware_status table.
3+
4+
Removes HardwareStatus entries that have no corresponding checkout_id in the LatestCheckout table.
5+
"""
6+
7+
import logging
8+
from django.core.management.base import BaseCommand
9+
from django.db import transaction
10+
from kernelCI_app.models import HardwareStatus, LatestCheckout
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
class Command(BaseCommand):
16+
help = (
17+
"Delete HardwareStatus entries with no corresponding checkout_id "
18+
"in the LatestCheckout table"
19+
)
20+
21+
def add_arguments(self, parser):
22+
parser.add_argument(
23+
"--dry-run",
24+
action="store_true",
25+
help="Show what would be deleted without actually deleting",
26+
)
27+
parser.add_argument(
28+
"--batch-size",
29+
type=int,
30+
default=1000,
31+
help="Number of records to delete per batch (default: 1000)",
32+
)
33+
34+
def handle(self, *args, **options):
35+
dry_run = options["dry_run"]
36+
batch_size = options["batch_size"]
37+
38+
valid_checkout_ids = set(
39+
LatestCheckout.objects.values_list("checkout_id", flat=True)
40+
)
41+
42+
orphaned_entries = HardwareStatus.objects.exclude(
43+
checkout_id__in=valid_checkout_ids
44+
)
45+
orphaned_count = orphaned_entries.count()
46+
47+
if orphaned_count == 0:
48+
self.stdout.write(
49+
self.style.SUCCESS("No orphaned HardwareStatus entries found.")
50+
)
51+
return
52+
53+
if dry_run:
54+
self.stdout.write(
55+
self.style.WARNING(
56+
f"[DRY RUN] Would delete {orphaned_count} entries. "
57+
"Run without --dry-run to execute deletion."
58+
)
59+
)
60+
return
61+
62+
self.stdout.write(
63+
f"Found {orphaned_count} HardwareStatus entries "
64+
"with no corresponding LatestCheckout."
65+
)
66+
67+
total_deleted = 0
68+
while True:
69+
with transaction.atomic():
70+
batch_ids = list(
71+
orphaned_entries.values_list("checkout_id", flat=True)[:batch_size]
72+
)
73+
74+
if not batch_ids:
75+
break
76+
77+
deleted_count = 0
78+
deleted_count = HardwareStatus.objects.filter(
79+
checkout_id__in=batch_ids
80+
).delete()[0]
81+
82+
total_deleted += deleted_count
83+
self.stdout.write(
84+
f"Deleted batch of {deleted_count} entries "
85+
f"(total: {total_deleted}/{orphaned_count})"
86+
)
87+
88+
self.stdout.write(
89+
self.style.SUCCESS(
90+
f"Successfully deleted {total_deleted} orphaned HardwareStatus entries."
91+
)
92+
)

0 commit comments

Comments
 (0)