Skip to content

Commit 9fb8741

Browse files
hayase_yasuhiroclaude
andcommitted
Apply Redis Cluster hash tags in RedisStore for all keys
This implements a comprehensive solution for Redis Cluster compatibility by applying hash tags to all cache keys at the RedisStore level. Implementation: - Added applyHashTag() method to RedisStore - Special handling for flexible() cache keys to ensure related keys (value, created, lock) hash to the same slot - Applied to all RedisStore methods: get, many, put, putMany, add, increment, decrement, forever, lock, forget Key format: - Regular keys: {hash(key)}:key - Flexible created keys: {hash(originalKey)}:illuminate:cache:flexible:created:originalKey - Flexible lock keys: {hash(originalKey)}:illuminate:cache:flexible:lock:originalKey This ensures that Cache::flexible() works correctly on Redis Cluster including AWS ElastiCache Serverless (Valkey) while maintaining backward compatibility with non-cluster Redis. Reverted Repository.php and tests to original state as hash tags are now handled transparently at the RedisStore level. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0a669f4 commit 9fb8741

File tree

4 files changed

+98
-103
lines changed

4 files changed

+98
-103
lines changed

src/Illuminate/Cache/RedisStore.php

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,31 @@ public function __construct(Redis $redis, $prefix = '', $connection = 'default')
5959
$this->setConnection($connection);
6060
}
6161

62+
/**
63+
* Apply hash tag to a key for Redis Cluster compatibility.
64+
*
65+
* This ensures that related keys (like flexible cache keys) are stored
66+
* in the same Redis Cluster slot by prefixing them with a hash tag.
67+
*
68+
* @param string $key
69+
* @return string
70+
*/
71+
protected function applyHashTag($key)
72+
{
73+
// Extract the original key from flexible cache keys
74+
if (preg_match('/^illuminate:cache:flexible:(created|lock):(.+)$/', $key, $matches)) {
75+
$originalKey = $matches[2];
76+
$hashKey = substr(md5($originalKey), 0, 4);
77+
78+
return "{{$hashKey}}:{$key}";
79+
}
80+
81+
// Apply hash tag to regular keys
82+
$hashKey = substr(md5($key), 0, 4);
83+
84+
return "{{$hashKey}}:{$key}";
85+
}
86+
6287
/**
6388
* Retrieve an item from the cache by key.
6489
*
@@ -69,7 +94,7 @@ public function get($key)
6994
{
7095
$connection = $this->connection();
7196

72-
$value = $connection->get($this->prefix.$key);
97+
$value = $connection->get($this->prefix.$this->applyHashTag($key));
7398

7499
return ! is_null($value) ? $this->connectionAwareUnserialize($value, $connection) : null;
75100
}
@@ -93,7 +118,7 @@ public function many(array $keys)
93118
$connection = $this->connection();
94119

95120
$values = $connection->mget(array_map(function ($key) {
96-
return $this->prefix.$key;
121+
return $this->prefix.$this->applyHashTag($key);
97122
}, $keys));
98123

99124
foreach ($values as $index => $value) {
@@ -116,7 +141,7 @@ public function put($key, $value, $seconds)
116141
$connection = $this->connection();
117142

118143
return (bool) $connection->setex(
119-
$this->prefix.$key, (int) max(1, $seconds), $this->connectionAwareSerialize($value, $connection)
144+
$this->prefix.$this->applyHashTag($key), (int) max(1, $seconds), $this->connectionAwareSerialize($value, $connection)
120145
);
121146
}
122147

@@ -140,7 +165,7 @@ public function putMany(array $values, $seconds)
140165
$serializedValues = [];
141166

142167
foreach ($values as $key => $value) {
143-
$serializedValues[$this->prefix.$key] = $this->connectionAwareSerialize($value, $connection);
168+
$serializedValues[$this->prefix.$this->applyHashTag($key)] = $this->connectionAwareSerialize($value, $connection);
144169
}
145170

146171
$connection->multi();
@@ -173,7 +198,7 @@ public function add($key, $value, $seconds)
173198
$connection = $this->connection();
174199

175200
return (bool) $connection->eval(
176-
LuaScripts::add(), 1, $this->prefix.$key, $this->pack($value, $connection), (int) max(1, $seconds)
201+
LuaScripts::add(), 1, $this->prefix.$this->applyHashTag($key), $this->pack($value, $connection), (int) max(1, $seconds)
177202
);
178203
}
179204

@@ -186,7 +211,7 @@ public function add($key, $value, $seconds)
186211
*/
187212
public function increment($key, $value = 1)
188213
{
189-
return $this->connection()->incrby($this->prefix.$key, $value);
214+
return $this->connection()->incrby($this->prefix.$this->applyHashTag($key), $value);
190215
}
191216

192217
/**
@@ -198,7 +223,7 @@ public function increment($key, $value = 1)
198223
*/
199224
public function decrement($key, $value = 1)
200225
{
201-
return $this->connection()->decrby($this->prefix.$key, $value);
226+
return $this->connection()->decrby($this->prefix.$this->applyHashTag($key), $value);
202227
}
203228

204229
/**
@@ -212,7 +237,7 @@ public function forever($key, $value)
212237
{
213238
$connection = $this->connection();
214239

215-
return (bool) $connection->set($this->prefix.$key, $this->connectionAwareSerialize($value, $connection));
240+
return (bool) $connection->set($this->prefix.$this->applyHashTag($key), $this->connectionAwareSerialize($value, $connection));
216241
}
217242

218243
/**
@@ -225,7 +250,7 @@ public function forever($key, $value)
225250
*/
226251
public function lock($name, $seconds = 0, $owner = null)
227252
{
228-
$lockName = $this->prefix.$name;
253+
$lockName = $this->prefix.$this->applyHashTag($name);
229254

230255
$lockConnection = $this->lockConnection();
231256

@@ -256,7 +281,7 @@ public function restoreLock($name, $owner)
256281
*/
257282
public function forget($key)
258283
{
259-
return (bool) $this->connection()->del($this->prefix.$key);
284+
return (bool) $this->connection()->del($this->prefix.$this->applyHashTag($key));
260285
}
261286

262287
/**

src/Illuminate/Cache/Repository.php

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -488,40 +488,35 @@ public function rememberForever($key, Closure $callback)
488488
*/
489489
public function flexible($key, $ttl, $callback, $lock = null, $alwaysDefer = false)
490490
{
491-
$hashKey = substr(md5($key), 0, 4);
492-
$valueKey = "{{$hashKey}}:{$key}";
493-
$createdKey = "{{$hashKey}}:illuminate:cache:flexible:created:{$key}";
494-
$lockKey = "{{$hashKey}}:illuminate:cache:flexible:lock:{$key}";
495-
496491
[
497-
$valueKey => $value,
498-
$createdKey => $created,
499-
] = $this->many([$valueKey, $createdKey]);
492+
$key => $value,
493+
"illuminate:cache:flexible:created:{$key}" => $created,
494+
] = $this->many([$key, "illuminate:cache:flexible:created:{$key}"]);
500495

501496
if (in_array(null, [$value, $created], true)) {
502497
return tap(value($callback), fn ($value) => $this->putMany([
503-
$valueKey => $value,
504-
$createdKey => Carbon::now()->getTimestamp(),
498+
$key => $value,
499+
"illuminate:cache:flexible:created:{$key}" => Carbon::now()->getTimestamp(),
505500
], $ttl[1]));
506501
}
507502

508503
if (($created + $this->getSeconds($ttl[0])) > Carbon::now()->getTimestamp()) {
509504
return $value;
510505
}
511506

512-
$refresh = function () use ($valueKey, $createdKey, $lockKey, $ttl, $callback, $lock, $created) {
507+
$refresh = function () use ($key, $ttl, $callback, $lock, $created) {
513508
$this->store->lock(
514-
$lockKey,
509+
"illuminate:cache:flexible:lock:{$key}",
515510
$lock['seconds'] ?? 0,
516511
$lock['owner'] ?? null,
517-
)->get(function () use ($valueKey, $createdKey, $callback, $created, $ttl) {
518-
if ($created !== $this->get($createdKey)) {
512+
)->get(function () use ($key, $callback, $created, $ttl) {
513+
if ($created !== $this->get("illuminate:cache:flexible:created:{$key}")) {
519514
return;
520515
}
521516

522517
$this->putMany([
523-
$valueKey => value($callback),
524-
$createdKey => Carbon::now()->getTimestamp(),
518+
$key => value($callback),
519+
"illuminate:cache:flexible:created:{$key}" => Carbon::now()->getTimestamp(),
525520
], $ttl[1]);
526521
});
527522
};

tests/Integration/Cache/MemoizedStoreTest.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -486,17 +486,12 @@ public function getPrefix()
486486
public function test_it_supports_with_flexible()
487487
{
488488
$this->freezeTime();
489-
490-
// Calculate the hash key for 'key'
491-
$hashKey = substr(md5('key'), 0, 4);
492-
$valueKey = "{{$hashKey}}:key";
493-
494489
Cache::flexible('key', [10, 20], 'value-1');
495490

496491
$this->travel(11)->seconds();
497492
Cache::memo()->flexible('key', [10, 20], 'value-2');
498493
defer()->invoke();
499-
$value = Cache::get($valueKey);
494+
$value = Cache::get('key');
500495

501496
$this->assertSame('value-2', $value);
502497
}

0 commit comments

Comments
 (0)