Datenbanken | PHP und Redis

Redis à la carte: Eine (verkannte) NoSQL-Datenbank
Kommentare

Der Remote Dictionary Service, kurz Redis, ist für viele leider nicht mehr als ein persistenter Memcache. Mit diesem Vorurteil möchte ich in diesem Artikel aufräumen. PHP verfügt, neben pRedis, einer reinen PHP-Implementierung, mit phpredis über eine in C geschriebene Extension für höhere Performance. In diesem Artikel werden wir mit der Extension arbeiten; die Implementierung der Beispiele mit pRedis gestaltet sich jedoch ähnlich. Als Demoapplikation schauen wir uns ein Modul auf der vor kurzem neu gelaunchten trivago-Plattform an. Es handelt sich um die Kartennavigation, die sich oben links auf der Seite befindet und fast vollständig auf Redis basiert.

Redis wird von Salvatore Sanfilippo entwickelt, der für VMWare arbeitet und dort die Möglichkeit bekommt, sich vollständig auf die Weiterentwicklung der Datenbank zu konzentrieren. Redis entstand Anfang 2009, um die Performance eines (Web-)Analytics-Produkts (namens LLOOGG [3]) aus Salvatores Startup zu verbessern. Mitte 2009 konnte er dann die zunächst eingesetzte MySQL-Datenbank vollständig durch Redis ablösen. Das Projekt wurde ein großer Erfolg und konnte sich innerhalb kürzester Zeit über eine wachsende Community freuen. Im März 2010 fand der bereits erwähnte Wechsel zu VMWare statt. Trotz der Festanstellung Salvatores bleibt Redis weiterhin unter der BSD-Lizenz verfügbar. Redis wird von vielen großen Projekten eingesetzt und hat eine aktive Community. Zu den bekanntesten Plattformen gehört wohl YouPorn, die schon durch den Einsatz von Symfony2 durch die PHP-Presse gegangen ist. Projekte wie EngineYard, GitHub, Flickr und StackOverflow gehören ebenfalls zu der Liste der Anwender, was Redis eine sehr breite Basis verschafft.

Redis? Ist das nicht wie Memcache mit Persistenz?

Kurze Antwort: Nein. Redis ist in mehreren Aspekten anders aufgebaut als Memcache. Memcache dient in erster Linie als Level 2 Cache, für manche sogar als Level 1 Cache. Redis hingegen ist eine Datenbank, wenn auch keine im relationalen Sinn.

Redis unterstützt neben den klassischen, aus Memcache bekannten Operationen zum Schreiben und Lesen von einfachen Daten ein deutlich weiteres Feld an Datentypen und Operationen. Die Datentypen teilen sich in Listen, Sets und HashMaps auf. Transaktionen (siehe Box „Transaktionen in Redis“) sind mit dem Command Set aus MULTI, EXEC, DISCARD und WATCH möglich. Hinzu kommt ab Version 2.6 auch eine Scripting-Schnittstelle in Lua.

Ein Feature, das selten in Artikeln auftaucht, jedoch umso häufiger Anwendung findet, ist das der PubSub Channels. Eine beliebige Anzahl von Clients baut jeweils eine persistente Verbindung zu einem Redis-Server auf und hört auf neue Nachrichten in einem Kanal. Ein anderer Client publiziert eine Nachricht, bestehend aus einem String, in diesem Kanal. Diese Nachricht wird dann an alle verbundenen Clients verteilt. Die Nachricht ist damit jedoch verloren, wenn der Client nicht genau in diesem Moment verbunden ist, denn es gibt für PubSub keine Persistenz. Diese Form der Eventverteilung ist vielleicht bekannt aus node.js, wo ein solches Vorgehen meist über (Web-)Sockets und Events gelöst wird. Der Vorteil der Redis-Implementierung liegt jedoch in der Freiheit der Wahl der Programmiersprache. Um PubSub eine Persistenz zu verleihen, geht man üblicherweise den Weg über Listen und schafft sich damit auch die Möglichkeit, z. B. eine Queue umzusetzen. Resque ist eine Implementierung in Ruby, die sich eben auf diesen Ansatz stützt.

Transaktionen in Redis

Transaktionen in einem Key-Value Store sind bisher einzigartig und ein klares Alleinstellungsmerkmal. Eine Transaktion in Redis läuft wie folgt ab:

> MULTI

OK

> INCR foo

QUEUED

> INCR bar

QUEUED

> EXEC

1) (integer) 1

2) (integer) 1

MULTI startet dabei die Transaktion. In MySQL gesprochen, ist es das BEGIN-Statement. Alle folgenden Kommandos werden in eine Queue gelegt und beim Aufruf von EXEC (dem MySQL COMMIT) ausgeführt. Redis garantiert dabei, dass entweder alle Kommandos ausgeführt werden oder keines – z. B. bei einem Verbindungsabbruch durch den Client. Wird die Persistenz durch das Append-only-Dateiformat betrieben, so garantiert Redis das Schreiben der Transaktion in einem Syscall.

WATCH: Das WATCH-Kommando ist ein Marker an einem Feld, der bei einer Veränderung und dem folgenden Zugriff aus einer Transaktion heraus die Transaktion fehlschlagen lässt. WATCH sichert also zu, dass die Transaktion fehlschlägt, wenn ein anderer Client genau die Daten verändert, auf denen gerade die Transaktion aufgebaut wird. Die Behandlung des Problems wird jedoch dem verbundenen Client überlassen.

Replikation

Eine Master-Slave-Replikation ist in Redis bereits eingebaut, eine Cluster-Funktionalität ist für die Version 2.8/2.9 in Planung. Beim Clustering handelt es sich um eine Funktionalität, die eine Verteilung der Schlüssel über eine Menge von Nodes vornimmt. Vorausgesetzt werden so genannte „Intelligent Clients“, die sich bei einer negativen Rückmeldung auf ein GET den anderen, in der Antwort benannten Node suchen. Das separate Sentinel-Projekt soll den Bedarf nach High-Availability stillen und ist aktuell ab der Version 2.6 RC8 zu haben. Somit ist bei Redis für deutlich mehr Ausfallsicherheit gesorgt. Einzig die Möglichkeit einer Master-Master-Replikation ist bisher nicht gegeben und wird durch das Cluster-Feature auch nicht ausreichend adressiert. Hat man verschiedene Data Center und möchte jeweils ein Master-Slave-Setup aufbauen, ist die Synchronisierung der beiden (oder mehr) Master nicht durch Bordmittel realisierbar. Hier müssen eigene Lösungen her, wie z. B. ein Multiplexer, der alle Anfragen auf mehrere Master verteilt, oder eigene Replikationsmechanismen, etwa in Form von PHP-Skripten. Bei trivago testen wir aktuell eine Variante, die sich auf das oben erwähnte PubSub-Feature stützt. Die Idee ist, die Applikation zweimal in den lokalen Master schreiben zu lassen: einmal in den korrekten Key, ein zweites Mal wird das komplette Statement, z. B. SET a b als String in den PubSub Channel gesendet. An der Position des anderen Masters läuft ein Daemon, der auf diesen Channel hört und die Kommandos „Cross-Data Center“ repliziert. Dieses Vorgehen ist für uns nur deswegen durchführbar, weil die Keyspaces beider Master ausreichend kollisionssicher sind. Für einen lastverteilenden Master-Master-Betrieb ist dieses Vorgehen eher nicht anwendbar und auch nicht zu empfehlen.

Redis lokal installieren

Redis ist für die meisten Linux- und Unix-Plattformen verfügbar. Auf der redis.io gibt es nur das Quellpaket zum Download. Allerdings sorgen Ubuntu, Arch Linux, FreeBSD und viele weitere Linux-Distributionen für die Verfügbarkeit von Binärpaketen. Für Apples OS X bekommt man Redis am ehesten durch MacPorts oder Homebrew. Windows-Nutzer finden auf der Downloadseite einen Link zu einer inoffiziell unterstützten Version. Die PHP Extension installiert sich unter Linux und OS X wie gewohnt und benötigt keine weitere Konfiguration. Unter Windows gibt es die Extension unter [5] für PHP 5.3 und unter für PHP 5.4 als fertige DLL. Als Bonus zum Redis-API bekommt man noch den Session Storage Adapter „geschenkt“, der sich, ähnlich wie die Memcached Extension, nativ über die php.ini konfigurieren lässt. Der Session-Adapter unterstützt die Angabe von mehreren Redis-Instanzen und akzeptiert zudem auch Gewichtungen zu jedem Server.

Redis bei trivago

Für die Kartennavigation auf der trivago-Plattform nutzen wir Redis nicht nur als Key-Value Store. Es kommen zusätzlich Listen zum Einsatz, mit denen wir auf sehr einfache Art die Anzahl der auf der Karte sichtbaren Labels begrenzen können. Redis bietet hierfür das LRANGE-Kommando, das einen Key, einen Startwert (0-basiert) und einen Endwert entgegen nimmt. Der Endwert darf negativ sein, was zu Folge hat, dass man mit KEY 0 -1 die gesamte Liste bekommt und mit KEY -1 -2 das letzte Element aus der Liste erhält. Man fühlt sich ein wenig an PHPs substr() erinnert und damit auch schnell heimisch. Redis bietet über diese einfache Listenfunktionalität noch weitaus mehr. Redis wird auch für trivagos Sidebar verwendet – ein Personalisierungfeature für registrierte Kunden. Hier werden Favoriten und die Historie der Suchen bzw. Detailansichten gespeichert. Allerdings kommen hier keine Listen zum Einsatz, sondern serialisierte Entitäten, da die Typsicherheit in diesem Fall die höhere Priorität hat.

Bei trivago verwenden wir die phpredis Extension, haben diese jedoch in ein selbst geschriebenes Connection Pooling eingebettet. Dieser Umstand ermöglicht es, dass pro Datenbank in Redis ein Pool erzeugt werden und zwischen lesenden und schreibenden Verbindungen unterschieden werden kann. Lesende Verbindungen werden durch den jeweiligen Slave abgehandelt, schreibende durch den Master. Die aktive Verbindungs-ID wird in der Session vorgehalten, neben einer Liste von Servern, die gerade nicht verfügbar sind. Ebenfalls unterscheiden wir ein persistentes Master-Slave-Paar und ein flüchtiges Paar – aus Performancegründen. Der flüchtige Stack ist so konfiguriert, dass beim Erreichen der in maxmemory eingetragenen Obergrenze die maxmemory-policy volatile-lru ist. Der persistente Stack hingegen ist mit noeviction konfiguriert. Für die beschriebenen Anwendungsfälle kommt trivago bisher mit insgesamt vier Instanzen (mit der bereits genannten Konfiguration) pro Data Center aus.

Eine Kartennavigation, das Datenmodell

Ein Pfad ist in trivagos Datenmodell eine ID zu einem geografischen Objekt. Es kann sich dabei um verschiedene Typen handeln, z. B. einen Kontinent, ein Land, ein Bundesland oder eine Stadt, jedoch auch um virtuelle Objekte wie z. B. den Schwarzwald. Die Klasse MapData (Listing 1) speichert den referenzierten Pfad, den dazugehörigen Elternpfad, eine Adresse zum Bild der Karte und die Karte als Base64 String. Die meisten modernen Browser können mit eingebetteten Bildern umgehen, somit spart man einen zusätzlichen Aufruf für das Bild. Dazu kommt noch eine Liste von Objekten vom Typ MapLink, auf die wir im weiteren Verlauf noch zurückkommen.

namespace TrivagoEntityNavigation;

class MapData
{
    /**
     * @var integer
     */
    public $iPathId;

    /**
     * @var integer
     */
    public $iParentPathId;

    /**
     * @var string
     */
    public $sMapUrl;

    /**
     * Base64 encoded image
     * @var string
     */
    public $sMapImgEnc;

    /**
     * @var TrivagoEntityNavigationMapLink[]
     */
    public $aMapLinks = array();
}

Der Redis Key baut sich aus den Bestandteilen Präfix, Version, Plattform und Pfad zusammen, getrennt durch einen Doppelpunkt. Ein solcher Schlüssel sieht für das Beispiel „Deutschland“ so aus: MapNav:0:DE:655. Diese Art von Schlüssel hat einige Vorteile, z. B. dass man über das KEYS-Kommando auf einfache Art Abfragen formulieren kann (siehe Box „Keys“). Auf weitere Vorteile soll später noch genauer eingegangen werden.

KEYS

Das KEYS-Kommando erlaubt es, Redis nach Keys zu fragen, indem man ihm ein Beispiel mit Platzhaltern gibt. Das Platzhalter-Set ist dabei das gleiche wie bei der PHP-Funktion glob(). KEYS arbeitet dabei mit einer Komplexität von O(n), was laut Dokumentation unter auf einem Einsteigerlevel-Laptop mit einer Geschwindigkeit von einer Millionen Keys in 40 Millisekunden abläuft.

  • MapNav:0:?E:655 trifft auf MapNav:0:DE:655 und MapNav:0:BE:655 zu
  • MapNav:*:DE:655 trifft auf MapNav:0:DE:655 und MapNav:1000:DE:655, aber auch auf MapNav:FooBarBazBla:DE:655 zu
  • MapNav:0:DE:65[56] trifft auf MapNav:0:DE:655 und MapNav:0:DE:656 zu.

Performance

Die Dokumentation weist deutlich darauf hin, diese Art der Abfrage nicht in der Applikation zu verwenden. trivago verwendet jedoch für die Karten eine eigene Datenbank, und die Anzahl der Einträge steigt nicht über 100 000, was eine adäquate Performance verspricht. Wir starten durchschnittlich acht Abfragen gegen Redis bei einem Seitenaufruf. Redis trägt dabei insgesamt knapp zwölf Millisekunden zur Gesamtzeit bei. In Spitzenzeiten fragen wir Redis zwischen 10 000 und 18 000 Mal pro Sekunde an, wobei die Last dabei nicht nennenswert hoch ist. Es gestaltet sich als weniger hilfreich, an dieser Stelle konkrete Zahlen anzubieten, da gerade der Begriff der Last ein sehr systemrelativer ist. In meinen lokalen (Intel Core i5 M460 @2.53GHz, 8 GB 1333er RAM, Redis 2.6.2, malloc=libc) Benchmarks, wobei ich dazu das von Redis mitgelieferte redis-benchmark verwendet habe, sieht es für GET und SET wie in Listing 2 aus. Für alle Benchmarks gilt: 50 Clients, 3 Bytes Payload und keep alive. Diese Angaben wurden aus Platzgründen aus dem Benchmark entfernt.

====== SET ======
  10000 requests completed in 0.08 seconds

100.00% <= 0 milliseconds
121951.22 requests per second

====== GET ======
  10000 requests completed in 0.09 seconds

100.00% <= 0 milliseconds
106382.98 requests per second

Hierbei fällt auf, dass Redis anscheinend schneller schreibt als liest. Das ist allerdings ein verbreiteter Irrglaube, der sich durch mehrere Wiederholungen des Benchmarks widerlegen lässt. Bemerkenswert (und durch Wiederholen der o. g. Benchmarks belegbar) ist jedoch, dass Redis keinen erwähnenswerten Performanceunterschied zwischen Lese- und Schreiboperationen hat. In Listing 3 finden wir noch die Performancedaten für LPUSH und LRANGE, gemessen an einem Element für LPUSH und 100 sowie 300 Elementen für LRANGE. Es gelten hier dieselben Rahmenbedingungen wie in Listing 2.

====== LPUSH (needed to benchmark LRANGE) ======
  10000 requests completed in 0.08 seconds

100.00% <= 1 milliseconds
121951.22 requests per second

====== LRANGE_100 (first 100 elements) ======
  10000 requests completed in 0.20 seconds

100.00% <= 1 milliseconds
49504.95 requests per second

====== LRANGE_300 (first 300 elements) ======
  10000 requests completed in 0.64 seconds

0.18% <= 1 milliseconds
73.25% <= 2 milliseconds
99.89% <= 3 milliseconds
100.00% <= 3 milliseconds
15649.45 requests per second

Der Zeitanstieg zwischen 100 und 300 Elementen ist annähernd linear und damit ausreichend verlässlich vorhersagbar für eine größere Menge an Elementen. Das Benchmark misst bis zu 600 Elemente, wobei wir dann bei – für diese Datenmenge durchaus akzeptablen – 7199,42 Anfragen pro Sekunde landen. Für die Kartennavigation bedeutet dies keine Performancegefahr, da es sich um eine Anzahl von 20 oder weniger Einträgen handelt, die über LRANGE abgefragt werden. Weitere Informationen können unter und nachgelesen werden.

Verlinkungen auf der Karte

Die Verlinkungen werden in einer separaten Entität behandelt, und diese werden als Liste wiederum in Redis abgelegt. Eine Liste bietet viele Möglichkeiten, das Verhalten für den Endbenutzer zu beeinflussen. Die Sortierung der Labels auf der Karte kann dabei eine große Rolle spielen. Legt man die Sortierung so aus, dass z. B. die Linkziele mit den meisten Hotels oder doch die mit den meisten Klicks in einer bestimmen Zeitperiode erscheinen? Über verschiedene Sortierungen lässt sich also auch ein A/B-Test gut abbilden. Die Antwort, welche Variante die bessere ist, liegt wohl immer an der gemessenen Zielgruppe. Für trivago funktioniert im Moment eine Mischung aus den beiden Varianten plus einer manuellen Nachkorrektur zufriedenstellend.

namespace TrivagoEntityNavigation;

class MapLink
{
    /**
     * @var integer
     */
    public $iPathId;

    /**
     * @var integer
     */
    public $iParentPathId;

    /**
     * @var integer
     */
    public $iLabelTopPos;

    /**
     * @var integer
     */
    public $iLabelLeftPos;

    /**
     * @var integer
     */
    public $iLabelWidth;

    /**
     * @var string
     */
    public $sLabelName;

    /**
     * @var integer
     */
    public $iIsAvailablePath;

    /**
     * @var integer
     */
    public $iCategoryType;
}

Wie versprochen kommen wir zu der Klasse, die in MapData als Array abgelegt wird (Listing 4). Es wiederholen sich die Eigenschaften des Pfads und des Elternpfads, jedoch dieses Mal in Bezug auf den Pfad in der MapData-Instanz, die die aktuelle MapLink-Instanz kapselt. Durch dieses Vorgehen wird eine Baumstruktur erzeugt, die den Navigationsfluss bzw. den möglichen Klickpfad des Benutzers darstellt.

Beim MapLink iPathId handelt es sich um den Zielpfad der Verknüpfung. IsAvailablePath sagt aus, ob trivago für diesen Pfad eine Suche anbieten kann, und CategoryType verweist auf den Typ des Pfads – also Stadt, Bundesland, Land, Kontinent etc. Der LabelName ist ein transientes Feld, dessen Wert zur Laufzeit anhand des Pfads und der aktiven Language-Locale-Kombination generiert wird. LabelWidth, LabelTopPos und LabelLeftPos helfen bei der Positionierung der Labels auf der Karte und werden vom Content-Team per Drag and Drop in einem Backend vergeben.

Insgesamt stellen diese beiden Entitäten die Grundlage für die Kartennavigation dar. Beide Entitäten verfügen zusätzlich über statische Factory-Methoden, um sie aus dem JSON-Format in eine Instanz des jeweiligen Typs zu transferieren. Das Ziel ist dabei lediglich, Typensicherheit zu garantieren und nicht mit einer stdClass arbeiten zu müssen. Die beiden werden durch einen Service verwaltet, den MapNavDataService. DataServices sind in der trivago-Architektur Teil der Datenbeschaffungsschicht und geben die vom Storage Adapter beschafften Werte in Form von Entitäten an andere Services weiter. Das Interface des DataService zeigt Listing 5 (Auszug):

use TrivagoComponentNoSQLStoreStoreException;

interface MapNavDataServiceInterface
{
    /**
     * Easier declaration of the prefix.
     *
     * @var string
     */
    const REDIS_PREFIX = 'MapNav';

    /**
     * Easier declaration of the devider.
     *
     * @var string
     */
    const REDIS_KEY_DIVIDER = ':';

    /**
     * Retrieves one entry from the store.
     *
     * This method guarantees a return value of type MapData or
     * null.
     * @param string  $sLocale
     * @param integer $iParentPathId
     * @return TrivagoEntityNavigationMapData
     */
    public function getData($sLocale, $iParentPathId);

    /**
     * Stores a new or existing entry in the store.
     *
     * @param string                             $sLocale
     * @param integer                            $iParentPathId
     * @param TrivagoEntityNavigationMapData $oData
     * @throws StoreException
     * @return void
     */
    public function storeData($sLocale, $iParentPathId, MapData $oData);

    /**
     * Returns the detail data for a given detail key.
     * This methods guarantees a return value of MapLink[] or empty array.
     *
     * @param string $sLocale
     * @param integer $iParentPathId
     * @return TrivagoEntityNavigationMapLink[]
     */
    public function getDetailData($sLocale, $iParentPathId);

    /**
     * Stores a new entry on the list stack for the given key.
     *
     * @param string                             $sLocale
     * @param integer                            $iParentPathId
     * @param TrivagoEntityNavigationMapLink $oDetailData
     * @throws RedisException
     * @return void
     */
    public function storeDetailData($sLocale, $iParentPathId, MapLink $oDetailData);

     /**
     * Key generation algorithm.
     *
     * @param string  $sLocale
     * @param integer $iParentPathId
     * @param bool    $bIgnoreMissingKeypart
     * @return string
     */
    public function generateDataKey($sLocale, $iParentPathId, $bIgnoreMissingKeypart = false);

    /**
     * Detailkey generation algorithm.
     *
     * @param string  $sLocale
     * @param integer $iParentPathId
     * @param bool    $bIgnoreMissingKeypart
     * @return string
     */
    public function generateDetailDataKey($sLocale, $iParentPathId, $bIgnoreMissingKeypart = false);
}

Das Interface verlangt sowohl Methoden zur Datenbeschaffung als auch zur Datenpersistenz. Wir schauen uns eine mögliche Implementierung der Methode getDetailData in Listing 6 an.

public function getDetailData($sLocale, $iParentPathId, $bIgnoreMissing = false)
    {
        $_sKey = $this->generateDetailDataKey($sLocale, $iParentPathId, $bIgnoreMissing);

        $_aMapLinks = array();
        try
        {
            $_aEntries = $this->getStore()->range($_sKey, 0, -1);
        }
        catch (StoreException $oException)
        {
           // fail silent, error is already logged by pooling
            return $_aMapLinks;
        }

        if (!empty($_aEntries))
        {
            foreach ($_aEntries as $_sEntry)
            {
                $_aMapLinks[] = MapLink::fromJsonString($_sEntry);
            }
        }
        return $_aMapLinks;
    }

Diese Implementierung ist simpel, da immer die gesamte Liste zurückgegeben wird. Für eine Liste, die aus mehreren Listenabschnitten (z. B. 0-5, 6-10 etc.) besteht, ist dieser Ansatz gänzlich ungeeignet. Über einen Dependency Injection Container ist es möglich, auf sehr einfache Weise verschiedene Implementierungen des Interfaces zu verwenden, die jeweils eine andere Ausleselogik implementieren können.

Ein wichtiger Aspekt ist die Auslagerung der Key-Generierung über die im Interface bedingten Methoden generateDataKey und generateDetailDataKey. Hier lassen sich durch Vererbung oder Traits verschiedene Key-Generierungsstrategien einbringen, um damit z. B. verschiedene Releasestände trennbar zu machen oder A/B-Testing zu ermöglichen. Die Keys sollten nicht zu lang werden, denn es verbraucht Platz, sie zu speichern.

Abfragelogik

Die Struktur des Schlüssels hat jedoch nicht nur den Sinn der guten Lesbarkeit, sondern auch geschäftliche und technische Gründe. trivago bedient aktuell Kunden in 33 Ländern in der jeweiligen Landessprache und Währung. Dies bedeutet allerdings auch, dass versucht wird, so gut wie möglich auf die Bedürfnisse und Ansprüche der jeweiligen Märkte einzugehen. Hierzu gehört auch, dass manche Staaten eine etwas andere Idee von Grenzverläufen haben oder manche Länder erst gar nicht anerkannt werden. Um den menschlichen und politischen Ansprüchen Genüge zu tun, wurde in die Abfragelogik ein Fallback-Mechanismus eingebaut, der es ermöglicht, (pro Plattform) jede Karte auf jedem Pfad zu überladen. Sofern keine Karte für die jeweilige Plattform gefunden wird, wird eine Variante gewählt, die einer „Lehrbuchkarte“ entspricht. Verwendet wird dabei eine simple exist-Abfrage, was im schlimmsten Fall zur Verdopplung der Redis-Anfragen führt. Um dieses Problem zu lösen, kann man eine Vorberechnung der zu verwendenden Karte in Betracht ziehen. Dieses Vorgehen birgt jedoch den Nachteil mangelnder Flexibilität, da es nur bei einer Neuberechnung möglich ist, neuen Content sichtbar zu machen.

Im Schlüssel gibt es einen weiteren Bestandteil, der dabei hilft, die Daten in Redis besser zu verwalten. Die Version im Schlüssel hilft bei vielen Anforderungen, primär jedoch bei der technischen Migration des Datenformats und dem A/B-Testing für verschiedene Kartengrafiken. Auch kann man versuchen, die Content Manager dazu zu bewegen, dass immer die Version n+1 gepflegt wird, und damit der aktuell auf der Plattform vorhandene Content nicht in Gefahr gerät. Der Nachteil der Lösung ist, dass auch hier die zu verwendende Version mit geringem Aufwand und schneller Verfügbarkeit vorgehalten werden muss oder pro neuer Version alle Einträge diese neue Version bekommen, unabhängig davon, ob der Inhalt sich wirklich geändert hat oder nicht. Letzteres führt jedoch zum inflationären Gebrauch von Versionen und sollte nur bei niedriger Änderungsrate in Betracht gezogen werden, da es in Redis keine Zeiger auf andere Einträge gibt (was jedoch als Feature in diesem Zusammenhang sehr interessant sein könnte).

Die Zukunft von Redis bei trivago

Es gibt Überlegungen, verstärkt auf die Vorberechnung von Daten zu setzen und damit Teilbereiche der Plattform ausschließlich aus Redis zu bedienen. Allerdings muss dafür noch die Möglichkeit einer Master-Master-Replikation oder einer Data-Center-orientierten Verteilung der Daten geschaffen werden, da ein einzelner Write Master für unsere Bedürfnisse absolut unzureichend ist. Es ist wahrscheinlich, dass Alternativen wie z. B. Riak oder RethinkDB in Betracht gezogen werden, um die Master-lose Verteilung zu übernehmen. Die verteilten Daten könnten dann wieder zur Geschwindigkeitssteigerung in Redis abgelegt werden. Doch das alles ist vorerst Spekulation, und es bleibt, den Performancegewinn zu beweisen und die Komplexitätssteigerung dagegen zu halten.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -