server/docs/features/jobbkø.md

87 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```sql
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