server/docs/features/jobbkø.md

4.4 KiB
Raw Blame History

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 LOCKED gir 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 med scheduled_for for periodisk kjøring