Skip to content

Commit aed967a

Browse files
author
hayase_yasuhiro
committed
Implement server-side cluster mode detection for Redis Cache
This commit implements runtime cluster mode detection using the Redis INFO command, enabling proper handling of single-endpoint cluster configurations (e.g., AWS Elasticache Valkey Serverless). Implementation details: - Add WeakMap-based cache for cluster mode detection per connection - Query server cluster mode via INFO cluster command - Handle different response formats between phpredis and predis: * phpredis: ['cluster_enabled' => 1] * predis: ['Cluster' => ['cluster_enabled' => '1']] - Apply cluster fallback to both putMany() and many() methods The implementation now correctly handles three connection scenarios: 1. Explicit cluster connections (PhpRedisClusterConnection, PredisClusterConnection) 2. Single-endpoint clusters (regular connection to cluster-mode server) 3. Non-cluster Redis servers (standard behavior) For cluster mode, cross-slot operations fall back to individual commands to avoid CROSSSLOT, MOVED, and driver-specific errors. Test results: 30/33 tests passing. The 3 failing tests are related to tagged cache flush() operations, which is a separate issue with RedisTaggedCache::flushValues() using multi-key DEL commands. Related to PR laravel#53940 which only handled explicit cluster connection types but not single-endpoint clusters.
1 parent 9d057cc commit aed967a

File tree

1 file changed

+67
-2
lines changed

1 file changed

+67
-2
lines changed

src/Illuminate/Cache/RedisStore.php

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ class RedisStore extends TaggableStore implements LockProvider
4545
*/
4646
protected $lockConnection;
4747

48+
/**
49+
* Cache for cluster mode detection per connection instance.
50+
*
51+
* @var \WeakMap
52+
*/
53+
protected $clusterModeCache;
54+
4855
/**
4956
* Create a new Redis store.
5057
*
@@ -57,6 +64,7 @@ public function __construct(Redis $redis, $prefix = '', $connection = 'default')
5764
$this->redis = $redis;
5865
$this->setPrefix($prefix);
5966
$this->setConnection($connection);
67+
$this->clusterModeCache = new \WeakMap();
6068
}
6169

6270
/**
@@ -92,6 +100,16 @@ public function many(array $keys)
92100

93101
$connection = $this->connection();
94102

103+
// In cluster mode, mget may not be supported for cross-slot keys
104+
// Fall back to individual get operations
105+
if ($this->needsClusterFallback($connection)) {
106+
foreach ($keys as $key) {
107+
$results[$key] = $this->get($key);
108+
}
109+
110+
return $results;
111+
}
112+
95113
$values = $connection->mget(array_map(function ($key) {
96114
return $this->prefix.$key;
97115
}, $keys));
@@ -132,8 +150,7 @@ public function putMany(array $values, $seconds)
132150
$connection = $this->connection();
133151

134152
// Cluster connections do not support writing multiple values if the keys hash differently...
135-
if ($connection instanceof PhpRedisClusterConnection ||
136-
$connection instanceof PredisClusterConnection) {
153+
if ($this->needsClusterFallback($connection)) {
137154
return $this->putManyAlias($values, $seconds);
138155
}
139156

@@ -346,6 +363,54 @@ protected function currentTags($chunkSize = 1000)
346363
}))->map(fn (string $tagKey) => Str::match('/^'.preg_quote($prefix, '/').'tag:(.*):entries$/', $tagKey));
347364
}
348365

366+
/**
367+
* Determine if cluster fallback is needed for the given connection.
368+
*
369+
* @param \Illuminate\Redis\Connections\Connection $connection
370+
* @return bool
371+
*/
372+
protected function needsClusterFallback($connection)
373+
{
374+
// Explicit cluster connections always need fallback
375+
if ($connection instanceof PhpRedisClusterConnection ||
376+
$connection instanceof PredisClusterConnection) {
377+
return true;
378+
}
379+
380+
// Check if server is in cluster mode (e.g., single-endpoint clusters)
381+
return $this->isServerInClusterMode($connection);
382+
}
383+
384+
/**
385+
* Determine if the Redis server is running in cluster mode.
386+
*
387+
* @param \Illuminate\Redis\Connections\Connection $connection
388+
* @return bool
389+
*/
390+
protected function isServerInClusterMode($connection)
391+
{
392+
// Use WeakMap to cache cluster mode detection per connection instance
393+
if (! isset($this->clusterModeCache[$connection])) {
394+
try {
395+
$info = $connection->client()->info('cluster');
396+
397+
// Handle different formats from phpredis vs predis
398+
// phpredis: ['cluster_enabled' => 1]
399+
// predis: ['Cluster' => ['cluster_enabled' => '1']]
400+
$clusterEnabled = $info['cluster_enabled']
401+
?? $info['Cluster']['cluster_enabled']
402+
?? null;
403+
404+
$this->clusterModeCache[$connection] = $clusterEnabled == 1;
405+
} catch (\Exception $e) {
406+
// If INFO command fails, assume not in cluster mode
407+
$this->clusterModeCache[$connection] = false;
408+
}
409+
}
410+
411+
return $this->clusterModeCache[$connection];
412+
}
413+
349414
/**
350415
* Get the Redis connection instance.
351416
*

0 commit comments

Comments
 (0)