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.
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.
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 |
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.
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.
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.
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.