# Infrastruktur: Jobbkø (PostgreSQL-basert) **Filsti:** `docs/infra/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 LOCKED` gir trygg concurrent polling uten låsekonflikt ## 3. Datastruktur ```sql CREATE TYPE job_status AS ENUM ('pending', 'running', 'completed', 'error', 'retry'); CREATE TABLE job_queue ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, 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 | | `meeting_summarize` | Møterommet | Generer møtereferat og action points fra transkripsjon | | `valgomat_generate_profile` | Valgomat | Generer syntetiske kandidatprofiler fra partiprogrammer | | `valgomat_moderation` | Valgomat | Semantisk deduplisering og nøytralitetsvask av brukerspørsmål | | `dictation_cleanup` | Lydmeldinger | AI-opprydding av diktert transkripsjon til strukturert notat | | `generate_embeddings` | Kunnskaps-Bridge | Generer vector embeddings for noder (pgvector) | | `prompt_eval` | Prompt-Laboratorium | Batch-evaluering av testsett mot valgte modeller | ## 6. Workspace-isolasjon Alle jobber merkes med `workspace_id`. Rust-workers kjører som superuser (bypasser RLS) og sikrer isolasjon i applikasjonskode: * Worker leser `workspace_id` fra jobben og bruker det til å lagre resultater tilbake i riktig silo * Workspace-spesifikk config (AI-prompts, navnelister) hentes fra `workspaces.settings` * Feilede jobber vises kun for brukere i riktig workspace i admin-visningen ## 7. Observabilitet - Jobber med `status = 'error'` skal være synlige i admin-visningen (SvelteKit `/admin/jobs`) - 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 - Opprett alltid jobber med riktig `workspace_id` — hent fra konteksten (innlogget bruker, webhook, etc.) - Ved `stats_parse`: denne erstatter den frittstående cronjobben beskrevet i podcast_statistikk.md — bruk jobbkøen med `scheduled_for` for periodisk kjøring