Zum Inhalt springen

Background-Jobs

Lalabase verarbeitet Aufgaben wie E-Mail-Versand, Benachrichtigungen und KI-Berechnungen asynchron über Solid Queue — das native Job-System von Rails 8. Die Jobs liegen in derselben PostgreSQL-Datenbank; ein separater Dienst wie Sidekiq ist nicht nötig.

Worker-Prozess

In der Container-Installation läuft der Worker als eigener Dienst neben der Web-Anwendung. Prüfe seinen Status mit:

docker compose ps
docker compose logs -f worker

Stockende Jobs erkennen

Stauen sich Jobs an, ist meist der Worker-Prozess gestoppt oder eine externe Abhängigkeit (z. B. der KI-Provider) nicht erreichbar. Ein Blick in die Worker-Logs zeigt die Ursache.

Nach einem Update

Starte den Worker nach jedem Update mit neu, damit er den aktuellen Code lädt — sonst verarbeitet er Jobs weiter mit der alten Version.

Wiederkehrende Jobs

Einige Aufgaben laufen nach einem festen Zeitplan (Solid-Queue-Recurring-Tasks in config/recurring.yml), z. B. das Aufräumen erledigter Jobs, geplante Konto-Löschungen und die Doku-Synchronisation.

Nicht verknüpfte Dateien aufräumen (DSGVO)

Beim Hochladen entsteht eine Datei im Speicher, bevor das zugehörige Formular abgeschickt ist. Wird das Formular nie abgeschickt — oder wird ein Bild in einen Text eingefügt, wieder entfernt und der Text gespeichert —, bleibt die Datei ohne Verknüpfung liegen. Rails räumt solche Dateien nicht von selbst ab.

Täglich um 05:15 Uhr löscht UnattachedBlobPurgeJob alle Dateien, die länger als sieben Tage ohne Verknüpfung sind. Erst ab dieser Löschung laufen sie in dieselben Sicherungsfristen wie alle übrigen Daten — ohne den Job liefen sie in gar keine.

Warum sieben Tage warten?

Zwischen dem Hochladen und dem Absenden eines Formulars ist eine noch nicht verknüpfte Datei technisch nicht von einer aufgegebenen zu unterscheiden. Die Wartefrist ist der Schutz davor, jemandem die Datei unter einem offenen Formular wegzulöschen. Dieser Job hat deshalb keine Scharfschalt-Variable: Er löscht ab dem ersten Lauf, die Sicherheit steckt in der Wartefrist und in einer Obergrenze pro Lauf.

Vorher ansehen, was gelöscht würde (löscht nichts):

bin/rails attachments:unattached_report

Was der Job getan hat, steht im Anwendungsprotokoll unter [unattached_blob_purge] — je gelöschter Datei eine Zeile mit Name, Typ, Größe und Datum, niemals mit Inhalt.

Verzeichnis gelöschter Dateianhänge (DSGVO)

Wird ein Dateianhang gelöscht, schreibt Lalabase einen Eintrag in ein Verzeichnis: Organisation, Dateiname, Typ, Größe, Trägerart und Zeitpunkt der Löschung. Der Inhalt der Datei wird nie erfasst. Das Verzeichnis belegt gegenüber dem Verantwortlichen, dass und wann gelöscht wurde (Rechenschaftspflicht, Art. 5 Abs. 2 DSGVO).

Zwei wiederkehrende Jobs halten es in Stand:

Job Zeit Aufgabe
AttachmentBackupConfirmationJob 05:45 trägt aus dem Protokoll des Sicherungsskripts nach, wann die Kopie aus Spiegel und Ablageort verschwunden ist
AttachmentDeletionRecordPurgeJob 06:00 löscht Einträge, die drei Jahre alt sind
Warum die Frist an der Löschung hängt

Die drei Jahre laufen ab der Löschung im Produktivsystem, nicht ab dem nachgetragenen Ablauf der Sicherungskopien. Der Nachtrag kann ausbleiben (Ausfall des Sicherungslaufs, verlorene Protokollzeile, eine Datei, die nie in eine Sicherung gelangt ist). Hinge die Frist daran, hätte so ein Eintrag gar kein Fristende — genau der Mangel, den das Verzeichnis belegen soll.

Der Nachtrag setzt voraus, dass der Worker das Protokoll des Sicherungsskripts lesen kann. Das Skript legt es root-eigen an; die Freigabe über eine Lesegruppe ist in ops/backup/README.md beschrieben. Fehlt sie, protokolliert der Job eine Warnung unter [deletion_register] statt still nichts zu tun.

Auskunft erteilen. Verlangt ein Verantwortlicher den Nachweis für seine Organisation, führen zwei Wege zur selben Aussage: die Seite Löschverzeichnis im Adminbereich oder der Bericht auf der Kommandozeile.

bin/rails deletion_register:report                        # alles
ORG=<organisation-uuid> bin/rails deletion_register:report

Beide nennen den Stichtag des Verzeichnisses und weisen Einträge ohne bestätigten Sicherungsablauf gesondert aus. Beides gehört in die Antwort: Vor dem Stichtag Gelöschtes ist nicht erfasst, und ein Eintrag ohne Nachtrag ist einer, dessen Ablauf niemand bestätigt hat. Die Auswahl der Organisation kommt aus dem Verzeichnis selbst und nicht aus der Organisationsliste, denn ein Eintrag überlebt die Organisation, die er betrifft.

Embedding-Aufräumung (DSGVO)

Für die semantische Suche und den KI-Assistenten speichert Lalabase abgeleitete Embedding-Vektoren. Wird der zugrunde liegende Inhalt gelöscht, entfernen die Löschpfade ihre Vektoren in aller Regel sofort und synchron (insbesondere bei personenbezogenen Daten wie Meeting-Transkripten und -Analysen).

Als zusätzliches Netz läuft täglich um 04:30 Uhr ein Aufräum-Job (Embeddings::OrphanGcJob), der verwaiste Vektoren einsammelt, deren Ursprungsinhalt über einen technischen Sonderweg ohne synchrone Bereinigung entfernt wurde. Dadurch persistieren abgeleitete Vektoren gelöschter Inhalte höchstens rund 24 Stunden.

Scharfschaltung (EMBEDDINGS_GC_ENFORCE)

Der tägliche Job startet aus Sicherheitsgründen im Probelauf: Er protokolliert nur, wie viele verwaiste Vektoren er einsammeln würde, löscht aber nichts. Prüfe die ersten Nächte die Log-Zahlen (oder DRY_RUN=1 bin/rails "embeddings:gc"). Sind die Zahlen plausibel, setze am Worker-Prozess die Umgebungsvariable EMBEDDINGS_GC_ENFORCE=1 — dann löscht der Job tatsächlich. Der manuelle Task unten (ohne DRY_RUN) löscht dagegen sofort scharf, unabhängig von dieser Variable.

Manuell anstoßen (z. B. nach einer großen Löschaktion):

bin/rails "embeddings:gc"          # ein globaler Lauf über alle Organisationen
DRY_RUN=1 bin/rails "embeddings:gc" # nur zählen, nichts löschen

Alte Vektorräume abräumen (Retention)

Wechselt eine Organisation ihr Embedding-Modell, bleibt der alte Vektorraum als Rückfallnetz liegen — man kann jederzeit dorthin zurückrollen. Wie viele Räume eine Organisation behält, steuert die Einstellung embedding_retention_limit (Standard: 2, also der aktive plus ein älterer). Derselbe nächtliche Job räumt alles darüber hinaus ab.

Zwei Räume rührt er dabei nie an: den, der gerade die Suche bedient, und einen, der gerade aufgebaut wird. Der Raum im Aufbau zählt außerdem nicht gegen das Limit — sonst würde ausgerechnet der Vergleichsraum verschwinden, während man zwei Modelle gegeneinander testet.

Eigene Scharfschaltung (EMBEDDINGS_RETENTION_ENFORCE)

Dieses Aufräumen hat einen eigenen Schalter, absichtlich getrennt von EMBEDDINGS_GC_ENFORCE. Der Orphan-Lauf löscht Vektoren, deren Ursprungsinhalt nachweislich weg ist — die sind wertlos. Hier dagegen werden intakte Vektoren gelöscht: das Rückfallnetz. Wer den einen Schalter umlegt, soll nicht ungefragt auch den anderen scharf schalten. Setze EMBEDDINGS_RETENTION_ENFORCE=1 am Worker-Prozess, wenn die Probelauf-Zahlen plausibel sind. Achtung: Der manuelle Task oben (bin/rails "embeddings:gc" ohne DRY_RUN) löscht auch die Räume jenseits des Limits sofort scharf — unabhängig von beiden Variablen. DRY_RUN=1 ist dort der einzige Schutz.

Zeitfenster-Hinweis für Rollbacks: Während rake embeddings:rollback läuft (Reaktivierung + Delta-Reindex — das dauert so lange wie das Korpus braucht), ist der Zielraum vorübergehend weder aktiv noch konfiguriert und damit für den Retention-Lauf ein regulärer Kandidat. Lege Rollbacks deshalb nicht in das nächtliche GC-Fenster (04:30), solange EMBEDDINGS_RETENTION_ENFORCE=1 gesetzt ist.

Was NICHT abgeräumt wird

Vektoren aus einem Modellwechsel, der vor Einführung der Raumverwaltung stattfand, gehören zu keinem registrierten Raum. Sie werden bewusst nicht gelöscht: dieselbe Signatur hat auch ein gerade erst konfiguriertes Ziel, dessen Aufbau noch nicht gestartet ist — auf Verdacht zu löschen würde einen laufenden Aufbau treffen. Der Job protokolliert sie stattdessen als UNREGISTERED. Wenn du dort Zeilen siehst, ist das eine Aufräum-Entscheidung für dich, keine Fehlfunktion.