4.4 KiB
4.4 KiB
Feature Spec: Jobbkø (PostgreSQL-basert)
Filsti: docs/features/jobbkø.md
1. Konsept
Et felles, sentralisert køsystem for alle asynkrone bakgrunnsjobber i Sidelinja. Bygget som en enkel tabell i PostgreSQL med Rust-workers som konsumerer jobber. Ingen ekstern message broker — PostgreSQL er køen.
2. Hvorfor PostgreSQL?
- Allerede i stacken, ingen ny infrastruktur å drifte
- Transaksjonell garanti: jobben og resultatet kan committes sammen med dataendringer
- Lavt volum (titalls jobber/time) gjør polling neglisjerbart
- Enkel feilsøking via SQL (
SELECT * FROM job_queue WHERE status = 'error') SELECT ... FOR UPDATE SKIP LOCKEDgir trygg concurrent polling uten låsekonflikt
3. Datastruktur
CREATE TYPE job_status AS ENUM ('pending', 'running', 'completed', 'error', 'retry');
CREATE TABLE job_queue (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
job_type TEXT NOT NULL, -- 'whisper_transcribe', 'openrouter_analyze', 'stats_parse', 'research_clip'
payload JSONB NOT NULL, -- Inputdata (filsti, tekst, tema_id, etc.)
status job_status NOT NULL DEFAULT 'pending',
priority SMALLINT NOT NULL DEFAULT 0, -- Høyere = viktigere
result JSONB, -- Resultatet ved fullført jobb
error_msg TEXT, -- Feilmelding ved error
attempts SMALLINT NOT NULL DEFAULT 0,
max_attempts SMALLINT NOT NULL DEFAULT 3,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
scheduled_for TIMESTAMPTZ NOT NULL DEFAULT now() -- For utsatte jobber / retry med backoff
);
CREATE INDEX idx_job_queue_pending ON job_queue (priority DESC, scheduled_for ASC)
WHERE status IN ('pending', 'retry');
4. Worker-arkitektur (Rust)
┌─────────────────────────────────────────────┐
│ Rust Worker-prosess (én per jobbtype) │
│ │
│ Loop: │
│ 1. SELECT ... FOR UPDATE SKIP LOCKED │
│ WHERE status IN ('pending','retry') │
│ AND job_type = $type │
│ AND scheduled_for <= now() │
│ ORDER BY priority DESC, scheduled_for │
│ LIMIT 1 │
│ │
│ 2. UPDATE status = 'running' │
│ 3. Utfør jobben │
│ 4a. OK: UPDATE status = 'completed' │
│ 4b. Feil: attempts += 1 │
│ Hvis attempts < max_attempts: │
│ status = 'retry' │
│ scheduled_for = now() │
│ + backoff(attempts) │
│ Ellers: status = 'error' │
│ │
│ Poll-intervall: 1 sekund (konfigurerbart) │
└─────────────────────────────────────────────┘
Backoff-strategi: Eksponentiell: 30s × 2^(attempts-1) (30s, 60s, 120s).
5. Jobbtyper
job_type |
Konsument | Beskrivelse |
|---|---|---|
whisper_transcribe |
Podcastfabrikken | Transkriber MP3 via faster-whisper |
openrouter_analyze |
Podcastfabrikken | Metadata-uttrekk fra transkripsjon |
research_clip |
AI Research-Klipper | Rens og strukturer innlimt tekst |
stats_parse |
Podcast-Statistikk | Batch-prosesser Caddy-logger |
6. Observabilitet
- Jobber med
status = 'error'skal være synlige i Produktivitetssuiten (enkel admin-visning) - Valgfritt: SpacetimeDB-event ved statusendring slik at UI kan vise fremdrift i sanntid (f.eks. "Transkriberer... 2/3 forsøk")
7. Instruks for Claude Code
- Implementer worker-logikken som et Rust-bibliotek (
sidelinja-jobs) som de ulike binærene kan bruke - Hver jobbtype får sin egen handler-funksjon, men deler polling-loopen
- Unngå å spinne opp mange tråder — én tokio-task per jobbtype er tilstrekkelig
- Aldri lagre lydfiler i
payload— bruk filstier - Ved
stats_parse: denne erstatter den frittstående cronjobben beskrevet i podcast_statistikk.md — bruk jobbkøen medscheduled_forfor periodisk kjøring