admin requeue-dead: honour the dedupe partial unique index
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 changed file +11 −3
modified
src/aiatlas/api/routers/admin.py
+11 −3
@@ -262,10 +262,18 @@ async def retry_job(job_id: str) -> dict[str, Any]: | ||
| 262 | 262 | |
| 263 | 263 | @router.post("/jobs/requeue-dead") |
| 264 | 264 | async def requeue_dead(kind: str | None = None) -> dict[str, Any]: |
| 265 | − where = "status = 'dead'" + (" and kind = :k" if kind else "") | |
| 265 | + where = "j.status = 'dead'" + (" and j.kind = :k" if kind else "") | |
| 266 | 266 | async with transaction() as conn: |
| 267 | − n = await fetch_val(conn, f"with u as (update jobs set status = 'queued', attempts = 0, error = null, locked_by = null, locked_at = null, finished_at = null, run_after = now() " | |
| 268 | − f"where {where} returning 1) select count(*) from u", k=kind) | |
| 267 | + # respect the partial unique index on dedupe_key (queued|running): skip dead jobs already superseded by a live one, | |
| 268 | + # and requeue only one dead job per dedupe_key (the most recent); the other duplicates are closed as superseded. | |
| 269 | + n = await fetch_val(conn, f"""with pick as ( | |
| 270 | + select distinct on (coalesce(j.dedupe_key, j.id)) j.id from jobs j | |
| 271 | + where {where} and (j.dedupe_key is null or not exists (select 1 from jobs q where q.dedupe_key = j.dedupe_key and q.status in ('queued','running'))) | |
| 272 | + order by coalesce(j.dedupe_key, j.id), j.created_at desc), | |
| 273 | + u as (update jobs set status = 'queued', attempts = 0, error = null, locked_by = null, locked_at = null, finished_at = null, run_after = now() | |
| 274 | + where id in (select id from pick) returning 1) | |
| 275 | + select count(*) from u""", k=kind) | |
| 276 | + await execute(conn, f"update jobs j set status = 'done', error = coalesce(error, '') || ' [superseded]' where {where}", k=kind) | |
| 269 | 277 | return {"requeued": int(n or 0)} |
| 270 | 278 | |
| 271 | 279 | |
| 272 | 280 | |