Files
archivdms/internal/storage/migrations/023_processing_jobs.sql
T
patrick 9a24ea29e1 FDN-01: repository & projektgerüst
Git-Repository für bestehenden archivdms-Code initialisiert, Branch-/Commit-Konvention (feature/<ticket>-<slug>-Branches, Ticket-Prefix in Commit-Nachricht) etabliert.
2026-08-11 21:27:53 +02:00

60 lines
3.2 KiB
SQL

-- PROJ: mandanten-job-queue (Phase 1+2)
-- Doku-only (siehe README.md in diesem Verzeichnis) — die tatsächliche
-- Ausführung passiert idempotent über
-- internal/storage/processing_jobs.go initProcessingJobsSchema(),
-- aufgerufen aus (*Store).initSchema() (zuletzt in der Kette, da die Tabelle
-- documents(id) FK-referenziert).
--
-- Mandanten-faire Verarbeitungs-Queue für die NACHGELAGERTE Dokument-
-- verarbeitung (OCR-Extraktion, Taxonomie-Autozuordnung, on_upload-Workflows).
-- Vorher lief das synchron im Upload-Request und erzeugte bei Batch-Scans /
-- SFTP-Massenuploads Lastspitzen.
--
-- * Kein Redis: die Queue ist eine ganz normale Postgres-Tabelle. Dokument-
-- INSERT und Job-INSERT laufen in EINER Transaktion
-- (CreateDocumentWithJob) — nie ein Dokument ohne Job, nie ein Job ohne
-- Dokument.
-- * WORM bleibt synchron im Request: Hash, Ablage unter
-- store/<tenant>/<yyyy>/<mm>/<sha256>.<ext> und chmod 0440 passieren VOR
-- dem INSERT. Der Job liest die archivierte Datei nur und schreibt
-- ausschließlich abgeleitete Metadaten.
-- * Dispatch: Round-Robin über die Mandanten (je Runde ein Job pro Mandant),
-- Locking über FOR UPDATE SKIP LOCKED.
-- * Retry: exponentielles Backoff über next_attempt_at (2^retry_count
-- Sekunden). Ab retry_count > max_retries (Default 5) bleibt der Job
-- dauerhaft 'failed' — kein Automatik-Retry mehr.
-- * derive_title: merkt sich, ob der Titel beim Staging nur ein Platzhalter
-- war und aus dem OCR-Text ersetzt werden darf. Ein vom Benutzer bzw. aus
-- dem SFTP-Dateinamen vorgegebener Titel wird nie überschrieben.
CREATE TABLE IF NOT EXISTS processing_jobs (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
document_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
status TEXT NOT NULL DEFAULT 'queued', -- queued | processing | done | failed
retry_count INT NOT NULL DEFAULT 0,
derive_title BOOLEAN NOT NULL DEFAULT false,
error_message TEXT,
next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(),
started_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
ALTER TABLE processing_jobs ADD COLUMN IF NOT EXISTS derive_title BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE processing_jobs ADD COLUMN IF NOT EXISTS next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now();
ALTER TABLE processing_jobs ADD COLUMN IF NOT EXISTS started_at TIMESTAMPTZ;
ALTER TABLE processing_jobs ADD COLUMN IF NOT EXISTS error_message TEXT;
CREATE INDEX IF NOT EXISTS idx_processing_jobs_dispatch
ON processing_jobs(tenant_id, status, next_attempt_at, created_at);
CREATE INDEX IF NOT EXISTS idx_processing_jobs_document
ON processing_jobs(document_id);
CREATE INDEX IF NOT EXISTS idx_processing_jobs_status
ON processing_jobs(status);
-- Anzeige-/Ablaufstatus am Dokument. Default 'done': der komplette Altbestand
-- wurde noch synchron verarbeitet und ist per Definition fertig — bewusst KEIN
-- Backfill-Skript, der Spalten-Default deckt alle Bestandszeilen ab.
ALTER TABLE documents ADD COLUMN IF NOT EXISTS processing_status TEXT NOT NULL DEFAULT 'done';