- Wafris ist ein Open-Source-Unternehmen für Web Application Firewalls, das einen Rails-Middleware-Client anbietet
- Der v1-Client benötigte einen lokalen Redis-Datenspeicher, in v2 wird jedoch SQLite verwendet
- Erklärt den Hintergrund der Entscheidung zur Migration von Redis zu SQLite, Performance-Überlegungen und Änderungen an der Architektur
TL;DR
- SQLite hat Dinge, die es gut kann, und Dinge, die es nicht gut kann
- Redis hat Dinge, die es gut kann, und Dinge, die es nicht gut kann
- Traditionelle RDBMS (Postgres/MySQL) haben Dinge, die sie gut können, und Dinge, die sie nicht gut können
- Diese Datenspeicher sind nicht direkt austauschbar, und wenn man es versucht, bekommt man Probleme
- Dieser Artikel beschreibt den Test- und Entscheidungsprozess beim Re-Architecting des Redis-basierten v1-Clients zum SQLite-basierten v2-Client
Faktoren, die die Änderung erzwangen
- Das Ziel von Wafris ist es, es Entwicklern leicht zu machen, ihre Websites zu schützen
- Wegen Problemen bei der Redis-Bereitstellung erreichte v1 dieses Ziel nicht vollständig
- Redis wurde gewählt, weil in Umgebungen wie Heroku damit leicht gearbeitet werden konnte, doch viele Nutzer hatten Probleme bei der Bereitstellung von Redis
- Nutzer zur Verwendung einer separaten Datenbank wie Redis zu zwingen, ist nicht nutzerfreundlich
Was ist „Geschwindigkeit“?
- Redis ist „schneller“ als traditionelle RDBMS, aber Verbindungen, Speicher, Prozesse usw. müssen dennoch verwaltet werden
- In Cloud-Umgebungen kann Netzwerklatenz ein großes Problem sein
- Da Wafris-Regeln bei jeder eingehenden HTTP-Anfrage ausgewertet werden müssen, kann Netzwerklatenz die Anwendung verlangsamen
Die Annahme eines Monolithen (Monolith-ish)
- Es gibt vollständig verteilte Anwendungen, aber die meisten Rails-Apps sind „Majestic Monoliths“
- Apps, die über mehrere Regionen bereitgestellt werden, in Server mit überlappenden Funktionen aufgeteilt sind oder nur teilweise Rails verwenden, verursachen bei Redis-Nutzung noch mehr Probleme
Architektur neu denken
- Wafris ist eine Web Application Firewall, die als Rails-Middleware installiert wird
- Vereinfacht in zwei Schritte aufgeteilt: 1) HTTP-Anfragen mit Regeln vergleichen und 2) das Verarbeitungsergebnis melden
- Das „Lesen“ der Regeln (Schritt 1) ist deutlich wichtiger als das „Schreiben“ (Schritt 2)
- Lesezugriffe müssen sequenziell verarbeitet werden, dürfen nicht fehlschlagen und beeinflussen die wahrgenommene Performance der Nutzer
- Schreibzugriffe können langsamer, gebündelt oder asynchron erfolgen
Enter SQLite
- Andere haben bereits gut erklärt, wofür SQLite geeignet ist
- SQLite konkurriert nicht mit Client/Server-Datenbanken, sondern mit
fopen()
- Es wurde erwartet, dass es durch den Wegfall der Netzwerk-Roundtrips deutlich schneller als Redis sein würde
- Es wurde entschieden, SQLite und Redis per Benchmark zu vergleichen
Benchmarking von SQLite und Redis
- Benchmarking ist eine dunkle Kunst, mit präzisen Zahlen sich selbst zu täuschen
- Benchmarking von Datenspeichern ist noch schwieriger
- Ziel war nicht, eine absolute Geschwindigkeit zu finden, sondern einen Benchmark zu erstellen, der auf unsere Daten und unseren Use Case zugeschnitten ist
- Optimierungs-Tweaks wurden ignoriert. Wafris soll einfach in eine App eingebaut werden und sofort funktionieren
- Es wurden nicht theoretische Benchmarks, sondern die Hot Paths der App und die schlimmste Query getestet
- Die schlimmste Query ist eine komplexe Anfrage an eine „lexical decimal“-Datenstruktur, die IP-Bereiche (IPv4, IPv6) auf Kategorien abbildet
- Bereichsabfragen wurden vorab berechnet und in SQLite-Tabellen sowie Redis Sorted Sets geschrieben
- Bei jeder eingehenden HTTP-Anfrage muss die Anfragen-IP mit benutzerdefinierten Allow-/Block-Bereichen, GeoIP-Bereichen und IP-Reputationsbereichen verglichen werden
Testprotokoll
- Getestet wurde auf einem M2 MacBook Air mit per Homebrew installiertem Redis und einer lokalen SQLite-Datenbank
- Es wurde mit dem bestehenden Bereichs-Datensatz (1,2 Millionen Einträge) getestet
- Mehrere IP-Sets wurden in derselben Reihenfolge gegen SQLite und Redis ausgeführt
- Für jedes Vielfache wurde der Test fünfmal ausgeführt und der Durchschnitt gebildet
Testergebnisse
- SQLite schlug Redis deutlich (in unserem speziellen Use Case)
- SQLite war etwa 3× schneller als eine lokale Redis-Instanz
- Das ist das Ergebnis noch vor Berücksichtigung der Netzwerklatenz
- Selbst wenn SQLite nur gleich schnell wie Redis wäre, wäre der Wegfall der Netzwerkzeit bereits ein Vorteil
Was im Diagramm fehlt
- Selbst wenn die SQLite-Performance im Benchmark doppelt so schlecht wäre, könnte sie in der Praxis wegen der Netzwerklatenz immer noch schneller sein
- Egal wie leistungsfähig der Redis-Server ist, es gibt Grenzen bei Netzwerkbandbreite, Verbindungen usw. sowie Latenz zwischen Regionen
- Mit SQLite ist nahezu unbegrenztes horizontales Scaling „kostenlos“ möglich
- Das Onboarding wird mit SQLite deutlich besser. Nutzer merken womöglich gar nicht, dass es verwendet wird
- Aus Redis ließe sich mehr Performance herausholen, aber es war schwer, Nutzer von Änderungen an ihrer Redis-Konfiguration zu überzeugen
Das Ergebnis ist erst der Anfang
- Es wurde zwar bewiesen, dass SQLite schneller ist als Redis, aber es gibt reale Trade-offs
- Die obigen Tests berücksichtigen keine Schreibvorgänge
- Um Konkurrenz zwischen Lesen und Schreiben zu verwalten, braucht es Datenbankverbindungen, Connection Pooling, Transaktionen usw.
- So wie ein elektrischer Supersportwagen schlecht dazu geeignet ist, Betonblöcke zu transportieren, sollte SQLite nicht für ungeeignete Aufgaben eingesetzt werden
Aufbau einer Synchronisierungsarchitektur
- In v1 (Redis) wurden beim Aktualisieren von Regeln im Wafris Hub auch die Regeln im Redis-Datenspeicher aktualisiert
- Mit SQLite funktioniert das nicht, weil nicht auf den Webserver „gepusht“ werden kann
- In v2 (SQLite): 1) Der Nutzer aktualisiert Regeln im Wafris Hub 2) der Client prüft in festen Intervallen auf aktualisierte Regeln 3) wenn Regeln aktualisiert wurden, wird eine vollständig neue SQLite-Datenbank heruntergeladen
- Dadurch sinkt die Verantwortung der Nutzer für Installation und Konfiguration erheblich
- Die Erfolgsquote bei der Installation des v2-Clients stieg um das Dreifache
Verteilte Architektur mit SQLite
- Betrachten wir eine Rails-App, die bei einem Cloud-Anbieter mit aktiviertem Auto-Scaling bereitgestellt wird
- Steigen die Anfragen von 100 req/s auf 10.000 req/s, werden die Compute-Instanzen skaliert, die Datenbank jedoch nicht
- Das ist in der Praxis ein Hauptgrund dafür, dass Rails-Apps wegen Überlastung ausfallen
- Durch die Synchronisierung der SQLite-Datenbank auf jede Compute-Instanz lässt sich dieses Problem lösen, weil alle Aufrufe lokal bleiben
Was ist mit Schreibvorgängen?
- Die App wurde in Lese- (Regelauswertung) und Schreibpfade (Reporting) aufgeteilt, danach wurde der Schreibpfad ignoriert
- Der Schreibpfad wurde neu entworfen als 1) asynchrone Verbindung zum Wafris Hub zum Melden 2) gebündelte Übertragung von Reports 3) vollständige Entfernung von Datenbank-Schreibvorgängen aus dem Client
- Das funktioniert vielleicht nicht für andere, aber uns interessieren nur Nutzer, die einen leicht bereitzustellenden und schnellen Wafris-Client wollen
Fazit
- Mit der v2-Architektur auf Basis von SQLite ist man sehr zufrieden
- Sie hilft bereits vielen Websites, Angriffe zu überstehen und online zu bleiben
- Der Einstieg ist deutlich einfacher geworden, wodurch sowohl der Support-Aufwand als auch der Aufwand für die Nutzer sinken
- Das ist ein Gewinn für ein sichereres und besser geschütztes Internet
7 Kommentare
SQLite ist zwar gut genug, aber in diesem Fall wirkte es irgendwie eher so, als wäre es einfach kein für Redis geeigneter Use Case gewesen …
Dass die Benchmarks auf einem M2 durchgeführt wurden, ist schon etwas ...
Muss man das dann für jede AWS-Instanz einzeln messen? Sie erwarten wirklich sehr viel von Open Source.
Wurde das in derselben Serverumgebung durchgeführt – ist das problematisch?
Muss man für Benchmarks eine bestimmte CPU verwenden...?
Welche Probleme könnten bei dem auf dem M2 durchgeführten Test auftreten? (Abgesehen davon, dass die reale Service-Umgebung keinen M2-Prozessor verwendet)
Genau das ist das Problem: im Labor experimentieren und dann behaupten, das sei perfekt für den kommerziellen Einsatz!