OBJECT ENCODING verstehen: Listpack vs. Hashtable und der automatische Umschaltpunkt
AI generated
SET
TTL
Redis · Interna · Speicheroptimierung
OBJECT ENCODING verstehen
Listpack vs. Hashtable und wann Redis automatisch umschaltet

Ein Redis-Hash mit drei Feldern und ein Redis-Hash mit dreißigtausend Feldern werden intern völlig unterschiedlich gespeichert, obwohl beide über dieselben Befehle wie HSET und HGET angesprochen werden. Redis wählt automatisch zwischen einer kompakten, speicherplatzoptimierten Listpack-Kodierung für kleine Collections und einer klassischen Hashtable-Struktur für große, abhängig von konfigurierbaren Schwellenwerten. Wer diesen automatischen Umschaltpunkt kennt, kann seine Datenmodelle gezielt so gestalten, dass Redis möglichst lange in der speichereffizienten Listpack-Kodierung bleibt. Dieser Artikel zeigt, wie das Umschalten technisch funktioniert und welche praktischen Konsequenzen sich daraus für die Speicherplanung ergeben.

12 Min. Lesezeit OBJECT ENCODING · Listpack Redis 7 · Redis 8 · Speicherplanung

1. Warum Redis für dieselbe Datenstruktur mehrere interne Kodierungen kennt

Redis-Datentypen wie Hash, List, Set und Sorted Set sind auf Befehlsebene einheitlich, intern speichert Redis sie aber je nach Größe und Inhalt in unterschiedlichen physischen Repräsentationen. Der Grund liegt im Speicherverbrauch: Eine vollwertige Hashtable mit Hash-Buckets, Zeigern und Kollisionsketten ist bei sehr vielen Einträgen effizient, weil sie konstante Zugriffszeiten unabhängig von der Elementanzahl bietet, verursacht aber pro Element einen spürbaren Speicher-Overhead durch Zeiger und interne Verwaltungsstrukturen.

Bei kleinen Collections mit wenigen Elementen überwiegt dieser Overhead den eigentlichen Nutzinhalt oft deutlich. Ein Hash mit drei kurzen Feldern würde in klassischer Hashtable-Kodierung mehr Speicher für Zeiger und Bucket-Strukturen verbrauchen als für die eigentlichen Daten. Redis begegnet diesem Problem mit einer kompakteren Alternativkodierung, die für kleine Collections den Overhead minimiert, auf Kosten linearer statt konstanter Zugriffszeit.

2. Das Grundprinzip von Listpack: kompakte, sequentielle Speicherung

Ein Listpack speichert alle Elemente einer Collection sequentiell hintereinander in einem einzigen zusammenhängenden Speicherblock, ohne separate Zeiger zwischen den Elementen. Jedes Element besteht aus einer kompakten Kodierung seiner Länge, gefolgt vom eigentlichen Wert und einem Rückwärts-Längenfeld, das es erlaubt, das Listpack auch rückwärts zu durchlaufen, ohne separate Indexstrukturen zu benötigen. Für einen Hash werden Schlüssel und Wert direkt hintereinander im selben Listpack abgelegt, für ein Sorted Set entsprechend Mitglied und Score.

Der Zugriff auf ein bestimmtes Element erfordert bei einem Listpack einen linearen Scan vom Anfang des Blocks, da es keine wahlfreien Zugriffsstrukturen gibt. Bei einer kleinen Anzahl an Elementen fällt dieser lineare Scan in der Praxis kaum ins Gewicht, weil moderne CPUs sequentiellen Speicherzugriff durch Cache-Lokalität sehr effizient verarbeiten, oft schneller als das Verfolgen mehrerer verstreuter Zeiger bei einer Hashtable mit vergleichbar wenigen Elementen.


# Kleinen Hash anlegen und Kodierung pruefen
HSET user:100 name Alice age 30 city Berlin
OBJECT ENCODING user:100
# -> "listpack"

# Kleines Sorted Set anlegen und Kodierung pruefen
ZADD leaderboard:daily 100 user:1 95 user:2 80 user:3
OBJECT ENCODING leaderboard:daily
# -> "listpack"

3. Der Umschaltpunkt zur klassischen Hashtable-Kodierung

Sobald eine Collection eine der konfigurierten Schwellenwerte überschreitet, entweder die maximale Elementanzahl oder die maximale Größe eines einzelnen Elements, wandelt Redis die Kodierung automatisch und dauerhaft in die klassische Hashtable-Kodierung um. Diese Umwandlung ist einseitig: Ein Hash, der einmal zur Hashtable-Kodierung gewechselt hat, wechselt auch dann nicht zurück zu Listpack, wenn nachträglich Elemente gelöscht werden und die Collection wieder unter die Schwellenwerte schrumpft. Redis optimiert bewusst nicht für diesen Rückwärtsfall, um die Kodierungslogik einfach zu halten und unnötige Umwandlungsoperationen bei schwankender Elementanzahl zu vermeiden.

Für Hashes steuern hash-max-listpack-entries und hash-max-listpack-value diesen Schwellenwert, für Sets set-max-listpack-entries und set-max-listpack-value, für Sorted Sets zset-max-listpack-entries und zset-max-listpack-value, und für Listen list-max-listpack-size. Sobald ein Element länger als der konfigurierte Wert für die maximale Elementgröße ist, etwa ein einzelner Feldwert eines Hashes, wandelt Redis die Kodierung um, selbst wenn die Elementanzahl insgesamt noch klein ist.


# Aktuelle Schwellenwerte für Hash-Kodierung anzeigen
CONFIG GET hash-max-listpack-entries
CONFIG GET hash-max-listpack-value

# Schwellenwerte anpassen (in redis.conf oder zur Laufzeit)
CONFIG SET hash-max-listpack-entries 128
CONFIG SET hash-max-listpack-value 64

4. Standardwerte in der Praxis: 128 Elemente, 64 Byte

In der Standardkonfiguration liegt der Schwellenwert für die Elementanzahl bei Hashes, Sets und Sorted Sets üblicherweise bei 128 Einträgen, der Schwellenwert für die maximale Größe eines einzelnen Elements bei 64 Byte. Diese Werte sind bewusst konservativ gewählt, um in typischen Anwendungsfällen einen guten Kompromiss zwischen Speicherersparnis und Zugriffsgeschwindigkeit zu bieten, ohne dass Anwender die Konfiguration überhaupt anfassen müssen.

Für Anwendungsfälle mit sehr vielen kleinen, aber überschaubar großen Collections lohnt es sich, diese Standardwerte gezielt zu überprüfen. Wird beispielsweise ein Hash mit typischerweise 150 kurzen Feldern verwendet, verpasst man mit dem Standardwert von 128 knapp die Listpack-Kodierung, obwohl die Collection inhaltlich noch klein genug wäre, um von den Speichervorteilen zu profitieren. Eine moderate Anhebung auf 200 könnte in diesem Fall spürbare Speicherersparnis bringen, ohne die Zugriffsgeschwindigkeit in der Praxis relevant zu verschlechtern.

5. Speicherersparnis konkret messen: MEMORY USAGE im Vergleich

Der tatsächliche Speicherunterschied zwischen Listpack- und Hashtable-Kodierung lässt sich mit dem Befehl MEMORY USAGE direkt messen, indem man dieselbe logische Datenstruktur einmal knapp unter und einmal knapp über dem konfigurierten Schwellenwert anlegt und den gemeldeten Speicherverbrauch vergleicht. In der Praxis zeigen sich für kleine Hashes häufig Einsparungen von deutlich mehr als der Hälfte gegenüber der klassischen Hashtable-Kodierung, abhängig von der Feldanzahl und der Länge der gespeicherten Werte.

Diese Messung sollte vor einer geplanten Anpassung der Schwellenwerte immer mit realistischen, aus der eigenen Anwendung stammenden Beispieldaten erfolgen, da die tatsächliche Einsparung stark von der Feldnamenlänge, der Wertlänge und der Elementanzahl der konkreten Collection abhängt und sich nicht pauschal auf jede Anwendung übertragen lässt.


# Speicherverbrauch eines Hashes direkt messen
HSET session:abc token xyz789 user_id 42 role admin
MEMORY USAGE session:abc
# -> (integer) 96  (Listpack-Kodierung)

# Denselben Hash kuenstlich über den Schwellenwert vergroessern
CONFIG SET hash-max-listpack-entries 2
HSET session:abc extra_field value
OBJECT ENCODING session:abc
# -> "hashtable"
MEMORY USAGE session:abc
# -> deutlich höherer Wert durch Hashtable-Overhead

6. Praktische Konsequenz für die Datenmodellierung

Die zentrale praktische Konsequenz aus dem Listpack-Mechanismus ist, dass viele kleine Collections speichereffizienter sind als wenige große, sofern die Zugriffsmuster das erlauben. Statt eines einzigen riesigen Hashes mit hunderttausenden Feldern, der zwangsläufig als Hashtable kodiert wird, kann eine Aufteilung in viele kleinere, thematisch gruppierte Hashes, die jeweils unter dem Schwellenwert bleiben, in Summe deutlich weniger Speicher verbrauchen, etwa durch Sharding nach Kategorie oder Zeitfenster statt eines globalen Sammel-Hashes.

Für ein Magento-Setup betrifft das etwa Session-Daten, Produktattribut-Caches oder Konfigurationswerte pro Shop-Store: Wird pro logischer Einheit, etwa pro Session oder pro Produkt, ein eigener, kleiner Hash verwendet, bleibt dieser in aller Regel deutlich unter den Standardschwellenwerten und profitiert dauerhaft von der Listpack-Kodierung, während ein einziger globaler Hash über alle Sessions hinweg zwangsläufig zur teureren Hashtable-Kodierung wechseln würde.

7. Sonderfall Listen: Quicklist als Kette von Listpacks

Bei Redis-Listen funktioniert die Kodierungslogik etwas anders als bei Hashes, Sets und Sorted Sets. Eine kleine Liste wird ebenfalls als einzelnes Listpack gespeichert, wächst die Liste jedoch über den konfigurierten Schwellenwert list-max-listpack-size hinaus, wechselt Redis nicht direkt zu einer klassischen verketteten Liste, sondern zu einer Quicklist, einer verketteten Struktur, deren einzelne Knoten selbst wiederum Listpacks sind.

Dieser Zwischenschritt kombiniert die Vorteile beider Ansätze: Innerhalb eines einzelnen Quicklist-Knotens bleibt der Speicher-Overhead durch die kompakte Listpack-Kodierung gering, während die Verkettung mehrerer Knoten das Wachstum der Liste über beliebig viele Elemente hinweg erlaubt, ohne dass ein einzelner Speicherblock unbegrenzt groß werden müsste. Für sehr lange Listen, etwa Warteschlangen mit tausenden Einträgen, ist dieses Verhalten weitgehend transparent und erfordert keine gesonderte Behandlung im Anwendungscode.

8. Monitoring im Produktivbetrieb: Kodierungsverteilung im Blick behalten

Für ein laufendes System lohnt sich gelegentliches Monitoring, welcher Anteil der Schlüssel eines bestimmten Präfixes tatsächlich noch in Listpack-Kodierung vorliegt. Da OBJECT ENCODING nur für einen einzelnen Schlüssel auf einmal funktioniert, empfiehlt sich für eine aggregierte Übersicht ein kleines Skript, das über SCAN alle Schlüssel eines Präfixes durchläuft und die Kodierung jedes einzelnen Schlüssels sammelt, statt KEYS zu verwenden, das den Server bei großen Datenmengen blockieren kann.

Ein plötzlicher Anstieg des Anteils an Hashtable-kodierten Schlüsseln innerhalb einer Datenklasse, die eigentlich klein bleiben sollte, ist oft ein früher Indikator für ein sich änderndes Nutzungsmuster in der Anwendung, etwa wachsende Produktattribut-Listen oder eine unerwartet hohe Anzahl an Session-Feldern, und sollte als Anlass genommen werden, sowohl die Datenmodellierung als auch die konfigurierten Schwellenwerte erneut zu überprüfen.


#!/bin/bash
# Kodierungsverteilung für alle Schlüssel mit Praefix "session:" ermitteln
redis-cli --scan --pattern 'session:*' | while read -r key; do
    redis-cli OBJECT ENCODING "$key"
done | sort | uniq -c

9. Grenzen des Mechanismus: kein Ersatz für sinnvolle Datenmodellierung

So nützlich die automatische Listpack-Kodierung für die Speicherplanung ist, sie ersetzt keine durchdachte Datenmodellierung. Ein Datenmodell, das ohnehin sehr viele Elemente pro Schlüssel erfordert, etwa eine globale Produktdatenbank mit hunderttausenden Einträgen in einem einzigen Sorted Set, profitiert von der Kodierungsschwelle nicht, weil die Hashtable-Kodierung hier ohnehin unvermeidlich und inhaltlich auch sinnvoll ist, da sie konstante statt linearer Zugriffszeit bei großen Elementmengen bietet.

Die Kodierungsschwelle ist deshalb vor allem als Optimierungshebel für Collections zu verstehen, deren Größe man ohnehin bewusst klein hält oder klein halten kann, nicht als Grund, künstlich kleine Collections zu erzwingen, wo eine größere, zusammenhängende Struktur inhaltlich die bessere Wahl wäre. Die Entscheidung zwischen vielen kleinen und wenigen großen Collections sollte primär vom Zugriffsmuster der Anwendung geleitet werden, die Speicherkodierung ist ein sekundärer, aber messbar wirksamer Optimierungsfaktor.

Datentyp Konfigurationsparameter (Anzahl) Konfigurationsparameter (Größe) Standardwert
Hash hash-max-listpack-entries hash-max-listpack-value 128 Einträge / 64 Byte
Set set-max-listpack-entries set-max-listpack-value 128 Einträge / 64 Byte
Sorted Set zset-max-listpack-entries zset-max-listpack-value 128 Einträge / 64 Byte
Liste list-max-listpack-size (kombiniert mit Knotenanzahl) 128 Einträge pro Quicklist-Knoten
Rückumwandlung Nicht vorhanden Nicht vorhanden Einmal Hashtable, immer Hashtable

Mironsoft

Cache-Layer-Setup und Magento-Redis-Integration

Magento-Cache, der nicht richtig greift oder falsch konfiguriert ist?

Wir richten Redis als Cache- und Session-Backend für Magento sauber ein, tunen Speicherverbrauch und Eviction-Strategien und sorgen dafür, dass Full Page Cache und Session-Storage zuverlässig zusammenspielen.

Redis-Setup

Cache-, Session- und FPC-Backend produktionsreif für Magento konfigurieren.

Memory-Tuning

Speicherverbrauch und Eviction-Policies auf die tatsächliche Shop-Last abstimmen.

High-Availability-Setup

Redis Sentinel oder Cluster für ausfallsichere Magento-Umgebungen einrichten.

10. Zusammenfassung

OBJECT ENCODING verstehen: Das Wichtigste auf einen Blick

Grundprinzip

Kleine Collections speichert Redis als kompaktes, sequentielles Listpack ohne Zeiger-Overhead, große als klassische Hashtable mit konstanter Zugriffszeit.

Umschaltpunkt

Konfigurierbare Schwellenwerte für Elementanzahl und maximale Elementgröße steuern den Wechsel, standardmäßig 128 Einträge beziehungsweise 64 Byte.

Einseitigkeit

Der Wechsel zur Hashtable-Kodierung ist dauerhaft, ein Schrumpfen der Collection führt nicht zurück zu Listpack.

Praxiskonsequenz

Viele kleine, thematisch getrennte Collections statt weniger großer Sammelstrukturen nutzen die Speichervorteile von Listpack gezielt aus.

11. FAQ: OBJECT ENCODING verstehen: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen Listpack- und Hashtable-Kodierung?
Listpack speichert alle Elemente sequentiell in einem einzigen kompakten Speicherblock ohne Zeiger-Overhead, Hashtable nutzt Hash-Buckets mit Zeigern für konstante Zugriffszeit, verbraucht aber pro Element mehr Speicher.
2Welche Konfigurationswerte steuern den Umschaltpunkt bei Hashes?
hash-max-listpack-entries für die maximale Elementanzahl und hash-max-listpack-value für die maximale Größe eines einzelnen Feldwerts, standardmäßig 128 Einträge beziehungsweise 64 Byte.
3Wechselt eine Collection nach dem Schrumpfen wieder zurück zu Listpack?
Nein, der Wechsel zur Hashtable-Kodierung ist einseitig und dauerhaft. Werden nachträglich Elemente gelöscht, bleibt die Collection trotzdem in Hashtable-Kodierung.
4Wie prüft man die aktuelle Kodierung eines Schlüssels?
Mit dem Befehl OBJECT ENCODING schlüssel, der für einen einzelnen Schlüssel entweder listpack, hashtable oder eine andere interne Kodierung zurückgibt.
5Wie unterscheidet sich die Kodierung von Listen von der anderer Datentypen?
Listen wechseln nicht direkt zu einer klassischen verketteten Liste, sondern zu einer Quicklist, einer verketteten Struktur aus mehreren Listpack-Knoten, die weiterhin von kompakter Speicherung innerhalb jedes Knotens profitiert.
6Wie misst man den tatsächlichen Speicherunterschied zwischen den Kodierungen?
Mit dem Befehl MEMORY USAGE schlüssel lässt sich der Speicherverbrauch derselben Datenstruktur knapp unter und knapp über dem Schwellenwert direkt vergleichen.
7Welche praktische Konsequenz ergibt sich für die Datenmodellierung?
Viele kleine, thematisch getrennte Collections, die jeweils unter dem Schwellenwert bleiben, verbrauchen in Summe oft deutlich weniger Speicher als wenige sehr große Sammelstrukturen.
8Sollte man die Standardschwellenwerte immer erhöhen, um mehr Listpack-Kodierung zu erzwingen?
Nicht pauschal. Eine Erhöhung lohnt sich nur, wenn typische Collections knapp über dem Standardwert liegen und Messungen mit MEMORY USAGE eine tatsächliche Speicherersparnis bestätigen.
9Wie überwacht man die Kodierungsverteilung im Produktivbetrieb?
Über ein Skript, das mittels SCAN alle Schlüssel eines Präfixes durchläuft und die Kodierung jedes Schlüssels mit OBJECT ENCODING sammelt, statt des blockierenden KEYS-Befehls.
10Ersetzt die automatische Kodierung eine durchdachte Datenmodellierung?
Nein, für Datenmodelle, die ohnehin sehr viele Elemente pro Schlüssel benötigen, ist die Hashtable-Kodierung unvermeidlich und sinnvoll. Die Kodierungsschwelle ist ein sekundärer Optimierungshebel, kein Grund für künstlich kleine Collections.