Skip to content

Commit

Permalink
Avoid writing multiple keys when using redis in cluster mode (#53940)
Browse files Browse the repository at this point in the history
* Avoid writing multiple keys when using redis in cluster mode

* formatting

---------

Co-authored-by: Taylor Otwell <taylor@laravel.com>
  • Loading branch information
bentleyo and taylorotwell authored Dec 17, 2024
1 parent 70607c5 commit f349abb
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 3 deletions.
20 changes: 17 additions & 3 deletions src/Illuminate/Cache/RedisStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@

use Illuminate\Contracts\Cache\LockProvider;
use Illuminate\Contracts\Redis\Factory as Redis;
use Illuminate\Redis\Connections\PhpRedisClusterConnection;
use Illuminate\Redis\Connections\PhpRedisConnection;
use Illuminate\Redis\Connections\PredisClusterConnection;
use Illuminate\Redis\Connections\PredisConnection;
use Illuminate\Support\LazyCollection;
use Illuminate\Support\Str;

class RedisStore extends TaggableStore implements LockProvider
{
use RetrievesMultipleKeys {
putMany as private putManyAlias;
}

/**
* The Redis factory implementation.
*
Expand Down Expand Up @@ -118,25 +124,33 @@ public function put($key, $value, $seconds)
*/
public function putMany(array $values, $seconds)
{
$connection = $this->connection();

// Cluster connections do not support writing multiple values if the keys hash differently...
if ($connection instanceof PhpRedisClusterConnection ||
$connection instanceof PredisClusterConnection) {
return $this->putManyAlias($values, $seconds);
}

$serializedValues = [];

foreach ($values as $key => $value) {
$serializedValues[$this->prefix.$key] = $this->serialize($value);
}

$this->connection()->multi();
$connection->multi();

$manyResult = null;

foreach ($serializedValues as $key => $value) {
$result = (bool) $this->connection()->setex(
$result = (bool) $connection->setex(
$key, (int) max(1, $seconds), $value
);

$manyResult = is_null($manyResult) ? $result : $result && $manyResult;
}

$this->connection()->exec();
$connection->exec();

return $manyResult ?: false;
}
Expand Down
18 changes: 18 additions & 0 deletions tests/Integration/Cache/RedisStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
namespace Illuminate\Tests\Integration\Cache;

use DateTime;
use Illuminate\Cache\RedisStore;
use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis;
use Illuminate\Redis\Connections\PhpRedisClusterConnection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Sleep;
use Mockery as m;
use Orchestra\Testbench\TestCase;

class RedisStoreTest extends TestCase
Expand All @@ -25,6 +28,7 @@ protected function tearDown(): void
parent::tearDown();

$this->tearDownRedis();
m::close();
}

public function testCacheTtl(): void
Expand Down Expand Up @@ -231,4 +235,18 @@ public function testMultipleItemsCanBeSetAndRetrieved()

$this->assertEquals([], $store->many([]));
}

public function testPutManyCallsPutWhenClustered()
{
$store = m::mock(RedisStore::class)->makePartial();
$store->expects('connection')->andReturn(m::mock(PhpRedisClusterConnection::class));
$store->expects('put')
->twice()
->andReturn(true);

$store->putMany([
'foo' => 'bar',
'fizz' => 'buz',
], 10);
}
}

0 comments on commit f349abb

Please # to comment.