Development

Redis: SCAN ist nicht SCAN

07. März 2025 Development

In einer Laravel-Anwendung ist es entscheidend, immer im Blick zu haben, welchen Redis-Client du verwendest. Unterschiedliche Redis-Clients (wie predis oder phpredis) können sich in Performance, Funktionsumfang und Konfiguration unterscheiden. Besonders bei der Implementierung des SCAN-Befehls gibt es Unterschiede, die nicht sofort offensichtlich sind.

Vor ein paar Tagen sind wir auf die Anforderung gestoßen, in einem riesigen Pool von Keys nach Ähnlichkeiten zu suchen. Bis dahin kein Problem.

1$keys = Redis::keys($pattern);
2 
3foreach ($keys as $key) {
4 Log::info($key);
5}

Normalerweise würde dieser Code die Anforderung erfüllen. Das Problem ist jedoch, dass die Anwendung, für die wir diese Funktionalität implementiert haben, mehrere Millionen Anfragen pro Tag verarbeitet, wodurch eine riesige Menge an Caching-Keys in Redis entsteht.

Die Lösung ist SCAN. Im Gegensatz zu KEYS, das alle Keys auf einmal abruft, SCAN iteriert über die Daten und benötigt einen Cursor, um die Ergebnisse zu verwalten. Aber soweit ist das noch nicht das Problem.

Unser nächster Versuch war ein klassischer Ansatz zur Iteration in Redis:

1$pattern = '/test/*'
2$cursor = '0';
3$count = 50000;
4do {
5 $result = $redis->scan($cursor, ['match' => $pattern, 'count' => $count]);
6 if ($result === false) {
7 break;
8 }
9 
10 $cursor = $result[0];
11 $allResults = array_merge($allResults, $result[1]);
12 
13} while ($cursor !== '0');

Ein erfahrenes Auge erkennt schnell, dass diese Implementierung vor allem für PREDIS ausgelegt ist, einen in PHP geschriebenen Redis-Client.

Ehrlich gesagt hat uns das überrascht. Während unsere Entwickler Predis verwendet hatten (vielleicht aus Bequemlichkeit beim Setup 😉), war in unserer Staging-Umgebung der PHPRedis-Client eingerichtet, der in C geschrieben ist.

Das Problem mit diesem Code ist: Er funktioniert nicht mit PHPRedis, oder liefert zumindest kein Ergebnis. Um das zu ändern, musst du den Code ein wenig anpassen.

1$pattern = '/test/*'
2$cursor = config('database.redis.client') == 'predis' ? '0' : null;
3$count = 50000;
4do {
5 $result = $redis->scan($cursor, ['match' => $pattern, 'count' => $count]);
6 if ($result === false) {
7 break;
8 }
9 
10 $cursor = $result[0];
11 $allResults = array_merge($allResults, $result[1]);
12 
13} while ($cursor !== '0');

Das Ergebnis dieses Unterschieds war absolut absurd. Aufgrund der leichten Abweichungen bei der Cursor-Verarbeitung zwischen den Clients hatten wir am Ende überhaupt keine Ergebnisse.

Behalte also immer im Blick, welchen Redis-Client du in Laravel verwendest! 🚀

Neugierig geworden?

Lust, gemeinsam zu kochen?

Ob Idee, Refactor oder Neubau — erzähl uns kurz von deinem Projekt. Wir melden uns innerhalb von 24 Stunden.