Redis im Überblick
Eine Einführung in die NoSQL-Datenbank mit Echtzeit-Charakter
Seit dem ersten Commit der Codebasis im Jahre 2010 hat sich Redis zu einer der führenden NoSQL-Datenbanken entwickelt. Der folgende Artikel gibt einen Überblick über diese Technologie und beschreibt den praktischen Einsatz von einigen Use Cases.
Vor knapp elf Jahren hatte ein Systemprogrammierer und IT-Sicherheitsexperte namens Salvatore Sanfilippo ein Problem: Für eine Web-Anwendung suchte er eine Datenbank, die zum einen extrem hohen Durchsatz aufwies und sich zum anderen nicht dem starren Korsett der damals vorherrschenden SQL-Datenbanken wie PostgreSQL oder MySQL unterwarf, da hier die Verarbeitung von unstrukturierten Daten im Vordergrund stand.
Also begann er eine Urversion des Remote Dictionary Servers zu schreiben, welche in kürzester Zeit als Redis einen Bekanntheitsgrad erlangte. Im Gegensatz zu anderen damals existierenden Ansätzen stand und steht bei Redis die Speicherung der Daten im Hauptspeicher im Vordergrund, was zu extrem kurzer Latenz und maximalem Durchsatz beitrug. Ferner war die Codebasis mit nur wenigen tausend Zeilen C sehr kompakt, was den Einsatz auch auf weniger leistungsfähiger Hardware interessant machte.
Doch bevor es jetzt in die Details geht, an dieser Stelle ein wenig mehr zum Thema NoSQL [1]. Früher einfach "Not SQL" genannt, beschreibt diese Abkürzung Datenbank-Technologien, die im Gegensatz zum herkömmlichen Ansatz der Structured Query Language (SQL) die Verarbeitung von nicht- bzw. nur teilweise strukturierten Daten erlaubt. Während in SQL zuerst eine Tabellenstruktur mit entsprechenden Felder definiert werden muss, bevor diese Struktur mit Daten gefüllt werden kann, erlauben NoSQL-Datenbanken mehr Freiheit bei der Verarbeitung von Daten. Die ursprüngliche Form waren einfache Key/Value Stores (wie auch Redis angefangen hat), wo einem Schlüsselnamen beliebige Werte zugeordnet wurden (typischerweise in der Form von Zeichenketten). Im Laufe der Jahre entwickelten sich z. T. immer spezialisiertere Formen von NoSQL-Datenbanken, die sich auf Dokumente, Zeitserien und Graphen fokussierten, um nur einige Beispiele zu nennen. Da parallel hierzu herkömmliche Datenbankhersteller ebenfalls entweder NoSQL-Erweiterungen ihrer SQL-basierten Produkte oder reine NoSQL-Datenbanken auf den Markt brachten, spricht man heutzutage von "Not only SQL"- Datenbanken
Ergänzend zu den ursprünglichen Zeichenketten, die am Beginn der Redis-Implementierung stand, sind im Laufe der Jahre eine Vielzahl von anderen Datentypen hinzugekommen, die Redis zu einem beliebten Werkzeug für Anwendungsprogrammierer machen. Im Folgenden die wichtigsten Datentypen ohne Anspruch auf Vollständigkeit [2]:
- Hashes stellen eine Zusammenfassung von Strings unter einem Schlüsselnamen dar. Analog zu normalen Zeichenketten in Redis sind diese "binary safe", können also beliebige Binärwerte außerhalb der üblichen Zeichendarstellung speichern. Hierzu gehören Audio- oder Video-Clips – komprimierte Zeichenfolgen, die andernfalls die Obergrenze von 512 MB pro Zeichenkette sprengen würden.
- Bitfelder erlauben eine optimierte Speicherung und Verarbeitung von einzelnen Bitfolgen, die die üblichen An- und Aus-Zustände widerspiegeln. Der Vorteil im Vergleich zu normalen Zeichenketten besteht in der kompakten Speicherung und schnellem Lese-/Schreibzugriff.
- Mengen: Analog zu dem algebraischen Grundtyp erlauben Mengen die Speicherung von Elementen ohne Duplikate. Operationen wie Vereinigung oder Schnittmenge erlauben die speichereffiziente und performante Verarbeitung von Mengendaten.
- Geordnete Mengen: Hierauf aufbauend ermöglicht es dieser Datentyp, eine Reihenfolge in Mengen zu implementieren, indem jedes Element um eine Punktzahl (Score) ergänzt wird, der die Position innerhalb der jeweiligen Menge reflektiert. Obwohl der Score normalerweise numerisch sein dürfte, sind hier beliebige Werte erlaubt, sodass die Implementierung von Ranglisten oder Empfehlungstabellen bei eCommerce-Applikationen ohne großen Aufwand möglich ist.
- Koordinaten: Redis erlaubt die Verarbeitung von Längen- und Breitengraden, so dass Abfragen wie "Ist ein Punkt in einem Radius um einen bestimmten Ort herum" mit wenig Implementierungsaufwand möglich sind.
- Probabilistische Datentypen: Diese Art von Datenstrukturen eignet sich für bestimmte Problemklassen, die mit einem potentiell ungenauen Ergebnis zurechtkommen, aber auf der anderen Seite den Vorteil haben, für große Datenmengen wenig Speicher zu benötigen. So ermöglicht es HyperLogLog[3], die Kardinalität einer Menge zu bestimmen, ohne die Menge vollständig im Speicher abzulegen. Anstatt die Elemente dieser zu speichern, werden hierbei lediglich die Hashwerte der Elemente gespeichert; da es bei dieser Vorgehensweise zu eventuellen Hash-Kollisionen kommen kann (d. h. zwei Elemente haben den gleichen Hashwert), die Wahrscheinlichkeit eines korrekten Ergebnisses kleiner 1. Einsatzgebiete für diese Art von Datentypen ist z. B. das schnelle Zählen von eindeutigen Benutzeridentifikationen bei Login-Diensten, etc.
- Streams: In der Funktionalität vergleichbar mit dem Open-Source-Projekt Kafka[4] werden Streams für die Implementierung von Message Queues und Bus-Systemen eingesetzt, bei denen es auf denen hohen Durchsatz in Kombination mit Skalierbarkeit ankommt. Hierbei erzeugt ein oder mehrere Klienten Daten, die in einer Warteschlange analog zu einer Logstruktur von einem oder mehreren Konsumenten verarbeitet werden. Hierbei tragen sogenannte Consumer Groups zur Skalierung bei: Konsumenten können Elemente einem Stream entnehmen; Redis sorgt dann automatisch für die interne Buchführung, sodass kein Element zweimal aus der Warteschlange gelesen wird.
Dieses ist nur eine Darstellung der wichtigsten Datentypen; eine vollständige Diskussion inkl. Dokumentation der Befehle ist in der Referenz zu finden [2].
Neben diesen server-definierten Datentypen kennt Redis seit der Version 3 die Möglichkeit, die nativen Datentypen durch Benutzer- bzw. Applikationsspezifische Datentypen zu erweitern. Dieses wird durch sogenannte Module ermöglicht. Ein Modul ist eine Erweiterung, die beim Start des Redis-Servers in Form einer Bibliothek, die ausführbaren Code enthält, geladen wird. Die Motivation für Module liegt in der Tatsache, dass die nativen Datentypen zwar bereits einen großen Funktionsumfang bieten, es aber Anwendungsbereiche gibt, wo diese Funktionalität nicht ausreicht. So bietet z. B. das Modul RedisGraph eine zu dem Industriestandard OpenCypher kompatible Graphdatenbank [5], die es erlaubt, Graphen aus Knoten und Verbindungen anzulegen und zu verwalten. Graphen werden hierbei z. B. für die Modellierung von sozialen Netzwerken, Betrugserkennung und Netzwerkinfrastrukturen eingesetzt. Das Modul RedisJSON ermöglicht die Verarbeitung von Dokumenten. RedisJSON ermöglicht hierbei aufbauend auf der Javascript Object Notation (JSON) die Speicherung, Indizierung und das Suchen von Text in Dokumenten [6]. Andere Module ermöglichen die Verarbeitung von Zeitserien (RedisTimeseries), stellen eine performante Infrastruktur für Machine Learning auf Redis-Basis zur Verfügung (RedisAI) und implementieren verschiedene probabilistische Datentypen (RedisBloom). Anwendungen, die diese Module benutzen, können sich auf den hohen Datendurchsatz und die geringe Latenz verlassen, wie man sie vom nativen Redis her kennt.
Doch wie werden die Module von den jeweiligen Applikationen angesprochen? Analog zu den Standard-Klienten-Anbindungen gibt es für viele Programmiersprachen ebenfalls Bibliotheken, über die die Module angesprochen werden können. Abb. 1 zeigt die generelle Architektur:
- Auf der untersten Ebene kapselt hiredis den Netzwerkzugang aus Klientensicht. Man kann sich hiredis als Socket-Wrapper vorstellen, der entweder eine Schnittstelle auf TCP-Basis (für Redis-Server auf entfernten Knoten) oder Domänen-Socket-Basis (für Server auf dem gleichen Knoten) implementiert. hiredis kümmert sich hierbei lediglich um die gesicherte Übertragung von Netzwerkpaketen, die das Redis-Kommunikationsprotokoll Redis Serialization Protocol (RESP)[7] beinhalten und hat sonst keine weitere Funktion aus Applikationssicht.
- Darauf aufbauend implementieren sprachspezifische Interfaces die Anbindung an die jeweilige Programmiersprache der Applikation. Redis kennt Klienten für 53 Programmiersprachen [8], wobei für die meisten Programmiersprachen mehr als eine Klientenanbindung zur Verfügung steht. So stehen z. B. für die im Enterprise-Umfeld häufig verwendete Sprache Java 13 Klienten zur Verfügung, die mit unterschiedlicher Komplexität verschiedene semantische Funktionalität besitzen (asynchrone Server-Kommunikation, Client-Side Caching, etc.).
- Die letzte Stufe bilden modul-spezifische Anbindungen, die den Funktionsumfang des jeweiligen Moduls auf Klientenseite zur Verfügung stellen. So erlaubt redis_graph das Anlegen und die Verarbeitung von Graphen mittels der immer beliebter werdenden Programmiersprache Rust, während redisgraph-py die gleiche Funktionalität für Python implementiert. Analog zu den vielfältigen Implementierungen der Standardfunktionalität von Redis existiert für die meisten Module mittlerweile mehr als eine Klientenanbindung; so gibt es beispielsweise für RedisGraph inzwischen drei JavaScript-, drei PHP- sowie zwei C#-Anbindungen. Da die Liste der Klientenanbindungen stetig wächst, empfiehlt sich ein Blick auf die jeweilige Webseite des Moduls, bevor es an die Implementierung geht, um sicherzustellen, dass diejenige Klientenanbindung ausgewählt wird, die optimal auf die jeweiligen Applikationsbedürfnisse zugeschnitten ist.
Da die Spezifikation der Modulschnittstelle veröffentlicht wurde und somit frei verfügbar ist [9], kann man natürlich ebenfalls seine eigenen Module programmieren, falls die vorhandenen Module nicht die jeweiligen Anforderungen erfüllen. Primäre Implementierungssprache für Module ist hierbei C/C++, allerdings hat dieses vorrangig historische bzw. Performance-Gründe. Da das Modul-Interface analog zu dem Rest der Server-Codebasis in C geschrieben ist, kann für die Programmierung von Modulen jede Sprache, die die entsprechenden C-Bindings unterstützt, verwendet werden. So ist z. B. die Version 2 von RedisJSON in der Sprache Rust geschrieben [6,10], ein entsprechendes Crate (ein Crate in Rust ist das Äquivalent zu einer Bibliothek in anderen Programmiersprachen) implementiert die Anbindung von Rust an die Modul-API [9].
Doch wie wird Redis in der Praxis eingesetzt? Neben den zahlreichen Open-Source-Projekten, die Redis als eine Abhängigkeit auf Plattformen für quelloffene Software wie z. B. Github definieren, findet man Redis mehr und mehr im kommerziellen Umfeld in Unternehmen jedweder Größe. Beispielhaft seien an dieser Stelle nur zwei Projekte angeführt, in denen Redis an zentraler Stelle Produktionsabläufe unterstützt.
Die Deutsche Börse setzt Redis im Bereich der finanz-regulatorischen BAFin-Anforderungsumsetzung ein [12]. Als intelligenter Cache unterstützt Redis die wichtigsten Plattformen dieses größten deutschen Handelsplatzes. Der Use Case ist hierbei die Echtzeiterfassung von Metadaten für jede Transaktion (Angebot, Kauf und Verkauf), die den regulatorischen Anforderungen unterliegt. Dabei dient Redis als Vorstufe für ein Datewarehouse, in dem die Daten nach entsprechender Bearbeitung aufbereitet für das finale Reporting gespeichert werden. Wichtig ist in diesem Zusammenhang der Echtzeit-Charakter bei der Erfassung und Verarbeitung der Daten: Sofern diese nicht vollständig für jedes relevante Finanzinstrument im Reporting erscheinen, drohen im Wiederholungsfall signifikante Probleme seitens der Finanzplatzaufsicht. Erwähnenswert ist in diesem Zusammenhang ebenfalls, dass die Deutsche Börse diese Plattform nicht nur für das interne Reporting benutzt, sondern diese Architektur als Basis entsprechendes Dienstleistungsangebot für externe Kunden zur Verfügung stellt.
Die größte europäische deutschsprachige Fußball-Fachzeitschrift Kicker setzt Redis im Bereich Content Delivery ein [13]. Redis sorgt hierbei als Bindeglied zwischen der Webserver-Landschaft bzw. dem Content Delivery Network und dem dahinterstehenden Content-Management-System für eine optimierte User Experience sowohl für den browserbasierten Desktop-Zugang aber auch für die strategisch gesehen immer wichtiger werdende Mobile-App für iOS und Android. Damit stellt Redis die Basis für Kickers internationales Wachstum in den Bereichen Reichweite und somit Benutzeranzahl dar.
Diese zwei Beispiele zeigen bereits die vielfältigen Einsatzmöglichkeiten von Redis. Mit dem Fokus auf In-Memory-Verarbeitung der Daten setzt diese NoSQL-Datenbank neue Maßstäbe im Bereich Echtzeitdatenverarbeitung. Zusätzliche Merkmale wie Skalierbarkeit durch Clustering sowie Hochverfügbarkeit ermöglichen das "Mitwachsen" dieser Datenbank beim Skalieren des App-Stacks, der diese Datenbank benutzt. Die vielfältigen Klientenanbindungen sowie die Module erlauben den Einsatz jenseits des traditionellen Caching Use Cases, der bei der initialen Programmierung von Redis im Vordergrund stand. Für die ersten Schritte mit Redis benötigt man lediglich einen C-Compiler wie gcc oder clang sowie ein Posix-kompatibles Betriebssystem (idealerweise entweder MacOS oder Linux). Nach dem Download des Quellcodes von Github [14] ist Redis innerhalb weniger Minuten kompiliert und die Programmierung kann beginnen.
- Wikipedia: NoSQL
- Redis: Redis Dokumentation
- Wikipedia: HyperLogLog
- Wikipedia: Apache Kafka
- Redis Labs: RedisGraph
- Redis Labs: redisjson 2
- Redis: Redis Serialization Protocol
- Redis: Klientenanbindungen
- Redis: Module SDK
- C. Zimmermann: RedisJSON FOSDEM-Präsentation
- Redis Labs: Redis module crate
- Redis Labs: Deutsche Börse Case Study
- Redis Labs: Kicker Case Study
- Redis Labs: Redis Quellcode