Files
NianAIGC/database/migrations/0002_generation_lifecycle_fencing.sql

70 lines
2.4 KiB
PL/PgSQL

-- Recoverable generation finalization and charge-before-dispatch fencing.
-- Additive so the previous release remains rollback-compatible.
alter table public.generation_jobs
add column if not exists dispatch_ready_at timestamptz;
alter table public.generation_jobs
add column if not exists finalized_at timestamptz;
alter table public.generation_jobs
add column if not exists provider_dispatch_started_at timestamptz;
-- Existing rows were created before dispatch fencing and are therefore ready.
update public.generation_jobs
set dispatch_ready_at = coalesce(dispatch_ready_at, created_at, now())
where dispatch_ready_at is null;
-- Rows already terminal before this migration have completed their legacy
-- finalization path and must not be replayed into billing/webhook side effects.
update public.generation_jobs
set finalized_at = coalesce(finalized_at, completed_at, updated_at, now())
where finalized_at is null
and status in ('succeeded', 'failed', 'expired', 'cancelled');
create index if not exists generation_jobs_finalize_claim_idx
on public.generation_jobs(finalized_at, scheduled_at, locked_at, priority desc);
create or replace function public.claim_generation_jobs(
p_worker_id text,
p_limit integer default 1,
p_lock_timeout_seconds integer default 300
)
returns setof public.generation_jobs
language plpgsql
set search_path = public, pg_temp
as $$
declare
v_now timestamptz := now();
begin
return query
with candidates as (
select id
from public.generation_jobs
where dispatch_ready_at is not null
and finalized_at is null
and status in ('queued', 'running', 'succeeded', 'failed', 'expired', 'cancelled')
and coalesce(scheduled_at, created_at) <= v_now
and (
locked_at is null
or locked_at < v_now - make_interval(secs => p_lock_timeout_seconds)
)
order by coalesce(priority, 0) desc, coalesce(scheduled_at, created_at) asc, created_at asc
limit greatest(1, least(coalesce(p_limit, 1), 20))
for update skip locked
),
updated as (
update public.generation_jobs
set locked_at = v_now,
locked_by = p_worker_id,
started_at = coalesce(public.generation_jobs.started_at, v_now),
updated_at = v_now
where id in (select id from candidates)
returning public.generation_jobs.*
)
select * from updated;
end;
$$;
revoke all on function public.claim_generation_jobs(text, integer, integer) from public;