- rqlite ist eine leichtgewichtige Open-Source-Distributed-Relational-Datenbank, in Go geschrieben und auf SQLite und Raft aufgebaut
- Die Entwicklung begann 2014, mit Fokus auf Zuverlässigkeit und Qualität; selbst nach mehr als zehn Jahren Entwicklung und Deployment wurden in Produktionsumgebungen weniger als 10 Panic-Fälle gemeldet
- Das Testen verteilter Systeme erfordert sorgfältige Überlegungen auf mehreren Ebenen und folgt einer Philosophie, die Qualität trotz Einfachheit bewahrt
Die Testpyramide: ein effektiver Ansatz
- Die Tests von rqlite folgen der „Testpyramide“
- Testpyramide: eine Struktur, die auf Unit-Tests basiert und Integrations-Tests sowie ein Minimum an End-to-End-Tests (E2E) umfasst
- Sie liefert eine effiziente, leicht zu debuggende und zielgerichtete Test-Suite
Unit-Tests: der Kern der Qualität
- Unit-Tests prüfen isolierte Komponenten und bieten ein Gleichgewicht aus Geschwindigkeit und Genauigkeit
- Dank SQLite und einer „Shared-Nothing“-Architektur lässt sich der Großteil der Funktionen mit Unit-Tests abdecken
- Vom gesamten rqlite-Code (ca. 75.000 Zeilen) entfallen rund 27.000 Zeilen auf Unit-Tests
- Die Tests laufen in wenigen Minuten durch, was häufiges Testen während der Entwicklung ermöglicht
Tests auf Systemebene: Konsens validieren
- Tests auf Systemebene validieren das Zusammenspiel zwischen dem Raft-Konsensmodul und SQLite
- Wichtige Testpunkte:
- Replikation von SQLite-Statements zwischen Nodes
- Leseoperationen bei unterschiedlichen Konsistenzstufen
- Validierung von Cluster-Fehlerbehebung und Leader-Wahl
- Mit etwa 7000 Zeilen Testcode werden Interaktionen in Single-Node- und Multi-Node-Konfigurationen umfassend abgedeckt
End-to-End-Tests: eine minimale Schicht
- End-to-End-Tests dienen als Smoke-Tests, die Systemstart, Clustering und grundlegendes Verhalten überprüfen
- Sie sind in Python geschrieben und validieren zentrale Funktionen, indem ein echtes rqlite-Cluster ausgeführt wird
- Beispiel: Validierung von Backups nach AWS S3
- Mit rund 5000 Zeilen Testcode wird ein bewusst begrenzter Ansatz verfolgt, um Debugging-Kosten zu minimieren
Performance-Tests: die Grenzen ausloten
- Performance-Tests bewerten Metriken wie:
- maximale
INSERT-Geschwindigkeit
- Verarbeitung gleichzeitiger Queries
- Vergleich von Speicher-, CPU- und Festplattennutzung
- Dazu gehören Tests mit SQLite-Datenbanken von mehr als 2 GB, um Speicherverwaltung und Engpässe bei Schreibvorgängen auf die Festplatte zu analysieren
- So werden Performance-Probleme erkannt und durch Optimierungen Stabilität sichergestellt
Erkenntnisse
- Früh mit dem Testen beginnen
- Unit-Tests sind der effektivste Weg, Vertrauen in das System aufzubauen
- Das Schreiben von Unit-Tests sollte während der Entwicklung nicht aufgeschoben werden, da sich Bugs damit schneller finden lassen als mit Integrations- oder E2E-Tests
- Testcode einfach halten
- Eine Test-Suite ist nicht der Ort für komplexes Refactoring oder das starre Festhalten am DRY (Don't Repeat Yourself)-Prinzip
- Wichtig ist, leicht verständlichen Code zu schreiben; zusätzlicher Boilerplate-Code sollte dabei akzeptiert werden
- Tests selbst validieren
- Beim Schreiben von Tests die erwarteten Ergebnisse vorübergehend ins Gegenteil verkehren und die Tests erneut ausführen
- Ein korrekt geschriebener Test muss in diesem Fall fehlschlagen, wodurch Fehler im Testcode frühzeitig verhindert werden können
- Testfehlschläge nicht ignorieren
- Auch schwer verständliche oder seltene Testfehlschläge liefern wichtige Informationen über die Software
- Schwer zu debuggende Fehlfälle sind oft eine Chance, kritische Schwächen im Code zu entdecken
- Determinismus maximieren
- Mechanismen schaffen, mit denen automatische Prozesse des Systems manuell ausgeführt werden können
- Beispiel: Die Raft-Snapshot-Funktion läuft normalerweise halbautomatisch, wurde aber so entworfen, dass sie sich in Tests explizit auslösen lässt
- Bewusst vorgehen (Be Deliberate)
- Höherwertige Integrations- oder E2E-Tests werden nur dann ergänzt, wenn ihre Notwendigkeit klar belegt ist
- Zu viele Tests können Entwicklungs- und Debugging-Geschwindigkeit verringern
- Anwenden und iterieren
- In Performance-Tests wurde der
fsync-Aufruf als zentraler Engpass identifiziert; deshalb werden Raft-Log-Entries vor dem Schreiben auf die Festplatte komprimiert, um die Festplattennutzung zu optimieren
- Effizienz priorisieren
- Eine Test-Suite, die in wenigen Minuten durchläuft, ermöglicht schnelle Entwicklungsiterationen
- Das ist ein entscheidender Vorteil für die Pflege und Lebendigkeit eines Open-Source-Projekts
Qualität zuerst
- Die Testpyramide wird eingehalten, und jede Testebene ist so gestaltet, dass sie einen klaren Zweck erfüllt
- Mit zunehmender Komplexität verteilter Systeme ist es entscheidend, die Einfachheit der Tests zu bewahren
- Ziel ist es, eine zuverlässige und einfach zu betreibende Datenbank zu bauen
1 Kommentare
Hacker-News-Kommentare
Der erste Test ist am schwierigsten, lohnt sich aber; danach werden weitere Tests einfacher.
Das Engagement für das Projekt ist beeindruckend.
Die Testpyramide ist nachvollziehbar, aber oft fehlen Ebenen, sodass man das schnell verbessern muss.
In den FAQ scheint es einen Copy-paste-Fehler zu geben.
Man freut sich auf einen Jepsen-Bericht.
Das Videoformat hat ebenfalls gefallen.
Das Setup für Performance-Tests ist beneidenswert.
Man hat rqlite ausprobiert, und es vermittelt seine Einfachheit gut.
Es wird nach Meinungen zu deterministischen Simulationstests gefragt.
Man fragt sich, ob rqlite in realen Umgebungen eingesetzt wird.
rqlite ist ein originelles und geniales Projekt.