Wie ein kaputter Installer-Reload zu zwei Open-Source-Patches führte
Von einer SQLite-Idee im TYPO3-Installer über eine PRAGMA-Reihenfolge-Falle bis zu zwei eingereichten Patches bei TYPO3 Core und DDEV.
Eine SQLite-Performance-Idee für den TYPO3-Installer, eine PRAGMA-Reihenfolge, die sich als echte Falle entpuppte, und ein Nebenschauplatz bei DDEV, den ich vorher gar nicht auf dem Schirm hatte: Wie aus einer Kleinigkeit zwei eingereichte Open-Source-Patches wurden.
Manchmal fängt man an, einen Bug zu debuggen, und landet zwei Wochen später bei zwei eingereichten Patches in zwei völlig unterschiedlichen Projekten. Genau das ist mir gerade passiert - und weil ich nett gebeten wurde, darüber einen Blogbeitrag auf meiner Seite zu schreiben, hier die Geschichte.
Die eigentliche Motivation
Ich hatte vor einiger Zeit schon mal eine kleine Sache zum TYPO3 Core beigetragen, und zuletzt eine eigene Extension veröffentlicht, die den TipTap-Editor von Sitegeist für TYPO3 um Unterstützung für das <abbr>-Tag erweitert. Beides zusammen hatte irgendwie wieder Lust gemacht, mich näher mit TYPO3 Core selbst zu beschäftigen.
SQLite hatte ich dabei schon länger im Hinterkopf - ich experimentiere seit einer Weile mit einer eigenen, etwas unkonventionellen TYPO3+SQLite-Architektur. Den konkreten Anstoß, daraus etwas für TYPO3 Core selbst zu machen, gab dann der dreiteilige Artikel Using TYPO3 for small projects, der SQLite als Datenbank für kleine TYPO3-Installationen empfiehlt - und dabei auf einen Tuning-Guide für SQLite-PRAGMA-Einstellungen verweist. Genau die Einstellungen, die ich vorher schon mal in einem eigenen Python-Skript verwendet hatte.
Der Ausgangspunkt: ein Installer, der beim Reload bricht
Ich hatte den TYPO3 Core (v15) ausgecheckt, um lokal mit DDEV daran zu arbeiten. Beim Durchklicken des Web-Installers hab ich - wie man das halt macht - zwischendurch mal das Browserfenster neu geladen. Ergebnis: eine Fehlermeldung, dass Datenbanktabellen fehlen.
Auf den ersten Blick nachvollziehbar - die Installation war ja noch nicht fertig. Aber der genaue Mechanismus dahinter war interessanter, als ich dachte: TYPO3s Installer hat keinen "ich bin jetzt bei Schritt X"-Zustand im klassischen Sinn. Stattdessen prüft er bei jedem Request neu, welcher Schritt gerade dran ist - und manche dieser Prüfungen setzen voraus, dass vorherige Schritte längst abgeschlossen sind. Kombiniert mit DDEVs Angewohnheit, für TYPO3-Projekte automatisch eine fertige MySQL-Konfiguration reinzuschreiben, sprang der Installer bei mir munter zwischen Schritten hin und her, die ich eigentlich nie zu Gesicht bekommen sollte.
Von SQLite-Defaults zu einer Installer-Idee
Die Idee war naheliegend: Warum bekommt man diese sinnvollen Performance-Defaults (WAL-Modus, synchronous = NORMAL, busy_timeout und Co.) nicht direkt im TYPO3-Installer, wenn man SQLite wählt? SQLite läuft ohne diese Einstellungen zwar, aber deutlich konservativer, als es für eine Webanwendung nötig wäre.
Der Patch: ein neuer Dropdown-Eintrag
Der Plan: ein zusätzlicher Eintrag in der Datenbank-Auswahl des Installers, "SQLite (performance mode, WAL)", neben der bestehenden manuellen SQLite-Option. Wählt man ihn, werden beim Anlegen der Datenbankdatei automatisch diese PRAGMAs gesetzt:
PRAGMA auto_vacuum = INCREMENTAL
PRAGMA journal_mode = WAL
PRAGMA synchronous = NORMAL
PRAGMA busy_timeout = 5000
PRAGMA cache_size = -20000
PRAGMA foreign_keys = ON
PRAGMA temp_store = MEMORY
Technisch ließ sich das über den vorhandenen initCommands-Mechanismus von TYPO3s Datenbank-Konfiguration lösen - ein Satz SQL-Befehle, die bei jeder neuen Verbindung ausgeführt werden. Kein Kernverhalten geändert, rein additiv, bestehende SQLite-Installationen bleiben unangetastet.
Die Falle: Reihenfolge ist alles
Beim ersten Testlauf zeigte mein Debug-Kommando fast alle PRAGMAs korrekt gesetzt - bis auf auto_vacuum, das hartnäckig bei 0 (also deaktiviert) blieb, obwohl es explizit in der Liste stand.
Der Grund ist eine echte SQLite-Eigenheit: auto_vacuum lässt sich nur auf einer wirklich leeren, 0-Byte-großen Datenbankdatei setzen. Sobald irgendein anderer Schreibvorgang stattgefunden hat - und genau das tut journal_mode = WAL sofort, weil dafür der Datei-Header geschrieben werden muss -, gilt die Datei nicht mehr als "leer", und auto_vacuum wird stillschweigend ignoriert. Kein Fehler, keine Warnung. Einfach nichts.
Die Lösung: Reihenfolge umdrehen, auto_vacuum als aller erstes PRAGMA setzen, bevor irgendetwas anderes die Datei "verbraucht". Danach stimmte auch dieser letzte Wert. Ein guter Kandidat für einen Regressionstest, damit niemand die Liste später "aus Versehen aufräumt" und genau in dieselbe Falle tappt.
Der Nebeneffekt: DDEV mischt sich ein
Beim Testen des fertigen Patches fiel auf: DDEV schreibt für TYPO3-Projekte automatisch eine additional.php mit fest codierten MySQL-Zugangsdaten - unabhängig davon, ob man einen MySQL-Container überhaupt hat. Wer omit_containers: [db] nutzt, weil er ja SQLite will, bekommt beim Installer plötzlich eine DNS-Fehlermeldung, weil TYPO3 brav versucht, sich mit einem Host namens db zu verbinden, den es gar nicht gibt.
Das war dann Runde zwei: ein Blick in DDEVs Go-Code (pkg/ddevapp/typo3.go), und ein kleiner, chirurgischer Fix - die betroffene Funktion überspringt den MySQL-Konfigurationsblock jetzt, wenn entweder der db-Container weggelassen wurde oder TYPO3 bereits erkennbar für SQLite konfiguriert ist. Alle anderen Einstellungen in der Datei bleiben unangetastet.
Zwei Patches, zwei Welten
Am Ende standen zwei eingereichte Änderungen:
- TYPO3 Core über Gerrit - inklusive Unittest für die PRAGMA-Reihenfolge und einem Changelog-Eintrag
- DDEV über einen ganz normalen GitHub-Pull-Request, mit Verweis auf den TYPO3-Patch als Kontext
Zwei komplett unterschiedliche Contribution-Workflows (Gerrit mit Change-Ids und Commit-Hooks vs. GitHub mit Conventional-Commit-Titeln), zwei komplett unterschiedliche Sprachen (PHP vs. Go) - aber beide lösen letztlich dasselbe Problem aus zwei verschiedenen Blickwinkeln.
Und dann kam der Kommentar
Auf dem Gerrit-Change hat sich Benni Mack gemeldet - kurz und positiv, mit dem Vorschlag, daraus einen Blogpost zu machen. Genau diesen Text liest du gerade.
Ich schreibe eigentlich selten selbst - dieser Blog läuft größtenteils automatisiert über eine KI-Pipeline, die fremde Artikel zusammenfasst und übersetzt. Diesmal war die Quelle aber ich selbst, und die Geschichte kam beim Erzählen zustande statt beim Zusammenfassen.
Beide Patches warten aktuell noch auf Review. Falls dich das Thema interessiert - der TYPO3-Change läuft unter Gerrit #94767, der DDEV-PR ist auf GitHub zu finden unter PR #8582