lobste.rs wechselt von MariaDB zu SQLite
(lobste.rs)- lobste.rs hat nach dem Wechsel von MariaDB zu SQLite auch den montäglichen Traffic-Anstieg stabil bewältigt und dabei geringere CPU- und Speicherauslastung sowie eine spürbar schnellere Reaktionsfähigkeit festgestellt
- Wird der separate MariaDB-VPS abgeschaltet, halbieren sich die VPS-Kosten, und das seit 2019 laufende Migrationsprojekt ist abgeschlossen
- Beim ersten Deployment erreichten allein schreibgeschützte Zugriffe sofort 100 % CPU-Auslastung, doch nach der Behebung von zwei Full Table Scans in großen Tabellen und einem N+1-Problem gelang das erneute Deployment
- Mithilfe benutzerdefinierter SQLite-Funktionen wurden
regexp,ifundstddevimplementiert; zugleich mussten Kompatibilitätseinschränkungen wie fehlendes unsigned bigint und das nur ASCII verarbeitendeNOCASEgelöst werden - Um die zugrunde liegende Datenbank ohne Zugriff auf die Produktionsdatenbank auszutauschen, braucht es Testdaten in realistischer Größe, eine Test-Suite, Deployment-/Rollback-Checklisten und enge Zusammenarbeit
Von der Diskussion 2019 bis zur Umstellung auf SQLite
- Die Diskussion über die Migration begann 2019 mit Issue #539
- Damals wurde MySQL als Alternative vorgeschlagen, um die Kompatibilität mit MariaDB zu berücksichtigen, während bereits eine Umstellung auf PostgreSQL diskutiert wurde
- 2025 erwähnte Rahul die Übernahme von MariaDB durch K1, woraufhin die Details einer PostgreSQL-Migration weiter besprochen wurden
- Im Februar fragte Rahul, ob lobsters auf SQLite laufen könnte, und ab Juni 2025 nahm die Mitarbeit am Projekt konkret Fahrt auf
- Der erste PR vom August 2025 wurde nach einem Arbeitsstopp von GitHub als veralteter PR geschlossen und konnte nicht erneut geöffnet werden
- Der zweite PR umfasste Performance-Tests, ein eigenes Migrationsskript zwischen Datenbanken und Debugging zur Datenintegrität
- Da keines der bestehenden Konvertierungsskripte für MariaDB/MySQL→SQLite die Anforderungen erfüllte, wurde ein separates Skript geschrieben
Fehlgeschlagenes erstes Deployment und Behebung der Performance-Engpässe
- Das erste Deployment am 21. Februar wurde mit einer vorbereiteten Deployment-Checkliste durchgeführt, doch direkt danach brachte schon der schreibgeschützte Traffic alle CPUs auf 100 %
- Da die Ursache nicht gefunden wurde, erfolgte ein Rollback
- Ohne Zugriff auf die Produktionsdatenbank war es schwierig, echte Performance-Probleme im Vorfeld nachzustellen
- Zwei Tage nach dem Fehlschlag wurde der dritte und letzte PR eröffnet, Suchprobleme wurden behoben und ein Skript zur Erzeugung großer Datenmengen hinzugefügt, das lokal einen Datensatz in halber Größe der echten lobsters-Daten erzeugt
- Die Datenerzeugung dauerte eine Woche
- Der Engpass beim ersten Deployment bestand aus Full Table Scans durch zwei Queries auf großen Tabellen sowie einem separaten N+1-Problem
- Dafür wurden Änderung 1, Änderung 2 und Änderung 3 umgesetzt
- Um weitere Performance-Probleme verfolgen zu können, wurde am Morgen des Deployment-Tages zusätzlich ein Slow Query Log ergänzt
Ergebnis des erneuten Deployments und niedrigere Betriebskosten
- Beim zweiten Deployment am 11. Juli wurden erneut Deployment- und Rollback-Checklisten vorbereitet; auch nach der Umstellung lief die Website normal weiter, und CPU- sowie Speichernutzung blieben stabil
- Während Site-Metriken und IRC überwacht wurden, konnten gemeldete Probleme mit Fix 1 und Fix 2 sofort behoben werden
- Auch beim Traffic-Anstieg am Montag gab es keine Probleme; bestätigt wurden geringere CPU- und Speicherauslastung sowie eine spürbar schnellere Reaktionsfähigkeit
- Mit der Abschaltung des MariaDB-VPS halbieren sich die VPS-Kosten, und das Migrationsticket kann geschlossen werden
Einschränkungen von SQLite im Vergleich zu MariaDB
- Über die Unterstützung für benutzerdefinierte Funktionen (UDF) im SQLite-Gem wurden die in SQLite fehlenden Funktionen
regexp,if,stddevimplementiert- Dadurch waren keine übermäßigen Workarounds für SQL-Migrationen nötig
- SQLite unterstützt das für einige IDs in MariaDB verwendete unsigned bigint nicht, daher wurde auf bigint umgestellt
- Auch die Sortierregeln sind eingeschränkter als bei MariaDB
- In MariaDB wurde
utf8mb4_general_civerwendet, in SQLite dagegenNOCASE NOCASEunterstützt kein vollständiges UTF-Case-Folding und verarbeitet nur ASCII-Zeichen
- In MariaDB wurde
- Für die SQLite-Full-Text-Search-Tabellen eignet sich statt der Voreinstellung die empfohlene Variante Contentless-Delete Tables
- Die standardmäßigen PRAGMA-Einstellungen von Rails funktionieren bei lobsters korrekt
- Da sich Datenbankmigrationen je nach DB unterscheiden, wurden bestehende Migrationen in das Verzeichnis old_migrations verschoben, sodass
db:migrateweiter funktioniert
- Da sich Datenbankmigrationen je nach DB unterscheiden, wurden bestehende Migrationen in das Verzeichnis old_migrations verschoben, sodass
Erkenntnisse aus Tests und Zusammenarbeit
- Im lobsters-Codebase fanden sich der Search Parser und heinous_inline_partials, ein Hack zur Beschleunigung des Renderings
- Dank der Test-Suite ließ sich die Korrektheit der SQLite-Umstellung ohne umfangreiche manuelle Tests verifizieren
- Reibungslose Kommunikation zwischen den Beteiligten war entscheidend für den Erfolg der Migration
- Ohne Zugriff auf die Produktionsdatenbank ist es sehr schwierig, die zugrunde liegende Datenbank auszutauschen
- Würde man die gleiche Aufgabe noch einmal angehen, sollte man vorab realistische Datenmengen vorbereiten
- Hätte man Tests so konfigurieren können, dass bei Full Table Scans ein Fehler ausgelöst wird, wären die Performance-Probleme des ersten Deployments früher aufgefallen
- Es werden Werkzeuge benötigt, mit denen sich produktionsnahe Datensätze leicht erzeugen lassen, ohne sie manuell zu schreiben oder jeweils eine Woche darauf zu warten
1 Kommentare
Meinungen auf Lobste.rs
@thomas0 hat die große Migration sorgfältig übernommen, @355E3B hat bei Planung und Betrieb geholfen, und dank der Mitwirkenden rund um #539 sowie #lobsters ließ sich auch die Nur-Lese-Zeit gut überbrücken.
Ursprünglich war in #539 PostgreSQL geplant, aber die Person, die die eigentliche Arbeit unterstützte, nutzte SQLite. Man wollte eine Lösung vermeiden, die größer und komplexer ist als der erwartete Bedarf, und entschied sich daher für SQLite. PostgreSQL ist normalerweise die Standardwahl, bringt aber den Aufwand mit sich, einen separaten Dienst zu betreiben, abzustimmen und zu warten.
Überraschenderweise sind nach der Migration sowohl CPU- als auch RAM-Nutzung gesunken; detaillierte Diagramme finden sich am Ende von #539.
Jedes Mal, wenn ich die Standardeinstellungen von SQLite sehe, bin ich überrascht. Es wäre gut, einen Modus für neue Projekte zu haben, der WAL, Fremdschlüssel,
synchronous=NORMALusw. standardmäßig bereitstellt; vermutlich gäbe es darüber hinaus noch weitere Default-Werte, die verbessert werden könnten.Ich frage mich, welche Backup-Strategie für die SQLite-Datenbank genutzt wird. Mich würde interessieren, ob ein Tool wie litestream verwendet wird oder ein anderer Ansatz.
Ich frage mich, wie lobste.rs Update-Vorgänge handhabt.
SQLite hat bereits die iif-Funktion, aber es ist auch interessant, in diesem Bereich überhaupt benutzerdefinierte Funktionen zu betreiben.
Ich dachte, SQLite unterstütze Nebenläufigkeit nicht auf dem Niveau von PostgreSQL oder MySQL. Wenn ich die Übersichtsdokumentation lese: Kann man es so verstehen, dass es inzwischen von mehreren Prozessen genutzt werden kann, lobste.rs aber auf einem einzelnen Server laufen muss?
Mich würde auch interessieren, ob sich die Prozesse innerhalb von SQLite über Shared Memory oder etwas wie
mmapsynchronisieren, sodass immer nur ein Schreibvorgang gleichzeitig möglich ist, aber mehrere Lesevorgänge erlaubt sind, und ob wartende Schreibvorgänge in einer Queue der Reihe nach warten.Für wartende Schreibvorgänge scheint keine explizite Shared-Memory-Queue verwendet zu werden, sondern ein kurzes Warten mit anschließendem erneuten Versuch. Ob SQLite über mehrere Maschinen hinweg völlig unmöglich ist, ist nicht sicher, aber der einfache Ansatz, die Datei per NFS zu teilen, wird ausdrücklich nicht empfohlen. Daher ist ein einzelner Server eine vernünftige Annäherung, und heutzutage kann auch eine einzelne Maschine eine beträchtliche Last bewältigen.
busy_timeoutfunktioniert. Als Referenzen dienen https://fractaledmind.github.io/2024/04/… sowie https://kerkour.com/sqlite-for-servers und https://www.sqlite.org/lang_transaction.html#immediate; nähere Details finden sich im zweiten Link im Abschnitt zuSQLITE_BUSYund im dritten Link.Das ist ein beeindruckendes Upgrade, bei dem PR, Issue und Gist die Details sehr gut dokumentieren. Ich frage mich, ob diese Änderung funktionale Unterschiede etwa bei der Seitensuche mit sich gebracht hat und wie groß die aktuelle SQLite-Datei auf der Festplatte ist.
Ich meine, irgendwo gelesen zu haben, dass die SQLite-Datei 3,8 GB groß ist.