Ver código fonte

Move the execution_time backfill into its own non-atomic migration

Batching the backfill bounded statement size but not lock duration: sharing
a transaction with the AddField meant the ACCESS EXCLUSIVE lock from ALTER
TABLE was held for the whole run, which is exactly the case the batching was
meant to help. 0025 goes back to adding the column only, and the backfill
moves to 0026 with atomic = False so the lock is released first.

The backfill now also skips rows which already have a value, making it
idempotent and letting an interrupted run simply be resumed. As a separate
migration it additionally reaches installations which had already applied
0025, rather than silently leaving their historical jobs unpopulated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Jeremy Stretch 2 semanas atrás
pai
commit
5346dab1c3

+ 0 - 28
netbox/core/migrations/0025_add_job_execution_time.py

@@ -1,28 +1,4 @@
 from django.db import migrations, models
-from django.db.models import DurationField, ExpressionWrapper, F
-
-BATCH_SIZE = 5000
-
-
-def populate_execution_time(apps, schema_editor):
-    """
-    Populate execution_time for existing jobs which have both a start and completion time recorded.
-    Updates are performed in batches, as installations which retain job history indefinitely can
-    accumulate a very large number of rows.
-    """
-    Job = apps.get_model("core", "Job")
-    queryset = Job.objects.filter(started__isnull=False, completed__isnull=False)
-    execution_time = ExpressionWrapper(F("completed") - F("started"), output_field=DurationField())
-
-    last_pk = 0
-    while True:
-        pks = list(
-            queryset.filter(pk__gt=last_pk).order_by("pk").values_list("pk", flat=True)[:BATCH_SIZE]
-        )
-        if not pks:
-            break
-        Job.objects.filter(pk__in=pks).update(execution_time=execution_time)
-        last_pk = pks[-1]
 
 
 class Migration(migrations.Migration):
@@ -37,8 +13,4 @@ class Migration(migrations.Migration):
             name="execution_time",
             field=models.DurationField(blank=True, editable=False, null=True),
         ),
-        migrations.RunPython(
-            code=populate_execution_time,
-            reverse_code=migrations.RunPython.noop,
-        ),
     ]

+ 46 - 0
netbox/core/migrations/0026_populate_job_execution_time.py

@@ -0,0 +1,46 @@
+from django.db import migrations
+from django.db.models import DurationField, ExpressionWrapper, F
+
+BATCH_SIZE = 5000
+
+
+def populate_execution_time(apps, schema_editor):
+    """
+    Populate execution_time for existing jobs which have both a start and completion time recorded.
+    Updates are performed in batches, as installations which retain job history indefinitely can
+    accumulate a very large number of rows. Rows which already have a value are skipped, so that an
+    interrupted run can simply be resumed.
+    """
+    Job = apps.get_model("core", "Job")
+    queryset = Job.objects.filter(
+        started__isnull=False, completed__isnull=False, execution_time__isnull=True
+    )
+    execution_time = ExpressionWrapper(F("completed") - F("started"), output_field=DurationField())
+
+    last_pk = 0
+    while True:
+        pks = list(
+            queryset.filter(pk__gt=last_pk).order_by("pk").values_list("pk", flat=True)[:BATCH_SIZE]
+        )
+        if not pks:
+            break
+        Job.objects.filter(pk__in=pks).update(execution_time=execution_time)
+        last_pk = pks[-1]
+
+
+class Migration(migrations.Migration):
+    # The backfill is deliberately kept out of the migration which adds the column, so that the
+    # ACCESS EXCLUSIVE lock taken by ALTER TABLE is not held for its duration. Running without a
+    # wrapping transaction is what allows the batching above to bound the work actually held open.
+    atomic = False
+
+    dependencies = [
+        ("core", "0025_add_job_execution_time"),
+    ]
+
+    operations = [
+        migrations.RunPython(
+            code=populate_execution_time,
+            reverse_code=migrations.RunPython.noop,
+        ),
+    ]