Run child tables cascade from run.id

Migration ccb21eacf5d5 deletes rows whose run is gone, then adds foreign keys
with ON DELETE CASCADE from run_node, run_metric and run_artifact to run.id.
Deleting runs is now the one statement. A node or metric written after its
run was deleted is dropped with an info line. Migrations run with foreign keys
off, since a batch rebuild of run would otherwise cascade into every child row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
2026-10-15 10:32:33 +02:00
co-authored by Claude Opus 5.5
parent ffa044724b
commit b4d7ffb982
6 changed files with 104 additions and 25 deletions
+4 -7
View File
@@ -851,10 +851,10 @@ async def retry_run(run_id: str, request: Request, user: CurrentUser) -> Any:
def _drop_runs(session: Session, chosen: Any) -> None:
"""A selection of runs and everything hanging off them.
The run tables carry a plain string ``run_id`` and no foreign key, so
nothing cascades on its own and these four statements are the whole of it.
``flow_run``, ``metric_minute`` and ``engine_event`` stay — they are the
observability record and are pruned on their own window.
The run tables' foreign keys cascade, so deleting the runs takes their
nodes, metrics and artifact rows with them. ``flow_run``, ``metric_minute``
and ``engine_event`` stay — they are the observability record and are
pruned on their own window.
Two things this costs, both deliberate. ``RunNode.outputs`` *is* the stage
cache, so a later run loses hits these would have served. And a node
@@ -864,9 +864,6 @@ def _drop_runs(session: Session, chosen: Any) -> None:
keeps whatever a ``run_artifact`` row or a live message still names, so
dropping the rows is enough and the hourly sweep reclaims the blobs.
"""
session.execute(delete(RunNode).where(col(RunNode.run_id).in_(chosen)))
session.execute(delete(RunMetric).where(col(RunMetric.run_id).in_(chosen)))
session.execute(delete(RunArtifact).where(col(RunArtifact.run_id).in_(chosen)))
session.execute(delete(Run).where(col(Run.id).in_(chosen)))