Mit v0.4.0 bekamen Workspaces Avatare. Die Funktion ist klein: Bild hochladen, es erscheint im Workspace-Wechsler. Der Upload klappte beim ersten Versuch. Das Bild tauchte überall auf, wo es hingehörte. Alle Tests waren grün.
Drei Tage später wäre es trotzdem verschwunden.
Zwei Wege, eine Datei zu verlieren
Uploads in Notez landen im Objektspeicher, bevor irgendetwas in der Datenbank auf sie zeigt. Das ist Absicht: Der Browser schickt die Bytes per Presigned POST direkt an S3, damit ein großer Upload nie die API belegt. Zuerst wird die Zeile geschrieben, die die Datei beschreibt, dann kommen die Bytes.
Diese Reihenfolge lässt eine Lücke. Wählt jemand eine Datei aus und schließt den Tab, landen die Bytes womöglich, ohne dass je etwas auf sie verweist. Der Speicher füllt sich mit Objekten, nach denen keine Notiz, kein Avatar und kein Nutzer je wieder fragt.
Deshalb gibt es einen Hintergrundjob. Einmal am Tag sucht er Dateien, die hochgeladen, aber nie irgendwo angehängt wurden, und löscht alle, die älter als drei Tage sind. Entscheidend ist dabei das Wort attached („angehängt“): ein Boolean, den der Upload selbst nicht setzt. Ein abgeschlossener Upload markiert die Datei als uploaded. Beanspruchen muss sie etwas anderes.
Bei Nutzer-Avataren ist dieses Etwas die Profilaktualisierung. Setzt du ein neues Bild, tut der Code außer dem Eintragen der URL in deinem Konto noch zwei Dinge: Er markiert die neue Datei als angehängt, und er stellt die ersetzte Datei zum Löschen in die Warteschlange. Beide Hälften zählen. Ohne die erste räumt der Aufräumjob ein Bild ab, das gerade benutzt wird. Ohne die zweite bleibt jeder Avatar, den du je gesetzt hast, für immer im Speicher und wird auf dein Kontingent angerechnet.
Workspace-Avatare taten keins von beidem. Die URL wurde beim Workspace eingetragen, das Bild wurde angezeigt, und die Datei lag da: hochgeladen, nicht angehängt, drei Tage vor der Löschung.
Warum nichts Alarm schlug
Die Tests liefen durch, weil sie das prüften, was ich gebaut hatte. Der Upload gibt eine URL zurück, die URL wird gespeichert, die Komponente zeigt sie an: alles wahr, und alles auch mit dem Fehler noch wahr. Kein Test prüfte einen Boolean in einer anderen Tabelle, weil ich nicht darauf gekommen war, dass ein Boolean in einer anderen Tabelle zur Funktion gehört.
Im Moment selbst ist der Fehler außerdem unsichtbar. Keine Fehlermeldung, keine Warnung in der Konsole, keine fehlgeschlagene Anfrage. Zwischen dem Bug und seinem Symptom liegen drei Tage und ein Cron-Lauf, und das Symptom ist ein kaputtes Bild auf dem Bildschirm von jemand anderem.
Aufgefallen ist es, weil ich den bestehenden Avatar-Code gelesen habe, bevor ich annahm, meiner sei gleichwertig. Der Ablauf für Nutzer-Avatare hatte zwei zusätzliche Schritte, die meinem fehlten, und die Kommentare erklärten genau, warum. Das spricht dafür, im Code den Grund festzuhalten und nicht nur den Mechanismus: Wer das Muster als Nächstes kopiert, muss wissen, welche Teile tragend sind.
Löschen ist auch eine Änderung
Als ich das Ersetzen repariert habe, kam ein dritter Fall ans Licht. Wird ein Workspace gelöscht, ist sein Avatar als angehängt markiert. Der Aufräumjob, der nur nicht angehängte Dateien abräumt, rührt ihn also nie an. Nichts zeigt mehr auf die Datei, und nichts wird je wieder auf sie zeigen. Sie ist verwaist, und zwar genau auf die Art, die der Aufräumjob nicht beheben kann.
Jetzt gibt ein Workspace beim Löschen auch sein Bild frei.
Das allgemeine Muster: Ein Hintergrundjob behält eine Datei nur, solange noch etwas auf sie verweist. Dann muss jeder Codepfad, der aufhört, auf eine Datei zu verweisen, das auch melden. Ersetzen. Leeren. Das übergeordnete Objekt löschen. Vergisst du einen Pfad, hast du ein Leck; drehst du einen um, verlierst du Daten. Von außen sehen beide Fehler völlig verschieden aus, und beide kommen von derselben fehlenden Zeile.
Was ich geändert habe
Ein gesetzter Workspace-Avatar markiert die Datei als angehängt, deshalb lässt der Aufräumjob sie in Ruhe.
Wird der Avatar ersetzt, kommt die alte Datei in die Warteschlange zum Löschen, mit demselben 30-tägigen Fenster zum Wiederherstellen wie alles andere, was du löschst.
Beim Löschen eines Workspace wird sein Avatar freigegeben, statt verwaist liegen zu bleiben.
Sieben Tests decken jetzt den Lebenszyklus ab, auch die Fälle, in denen nichts passieren darf: Einen Workspace umzubenennen darf seinen Avatar nicht zum Löschen vormerken, und dieselbe URL erneut zu speichern ist kein Ersetzen. Bevor ich den Tests vertraut habe, habe ich geprüft, dass sie ohne den Fix fehlschlagen.
Weiterlesen
Vier Wege, Verlorenes zurückzuholen: Rückgängig, Versionen, Papierkorb und das abgelehnte Speichern – was Rückgängig, 30 Tage Versionsverlauf, der Papierkorb und der Schutz vor Speicherkonflikten jeweils zurückholen und wo jeder davon endet.
Zugewiesen an jemanden, der nicht mehr hier arbeitet – warum das Entfernen einer Person aus einem Workspace ihre Zuweisungen löscht, statt einen ausgegrauten Namen an vierzig Notizen zu hinterlassen.
Notez: eine Notizen-App für Entwickler – was Notez ist, auf welchen Wegen eine Notiz hineinkommt und was jeder Plan enthält.