bug #30518 [Cache] Fix perf when using RedisCluster by reducing roundtrips to the servers (nicolas-grekas)
This PR was merged into the 3.4 branch.
Discussion
----------
[Cache] Fix perf when using RedisCluster by reducing roundtrips to the servers
| Q | A
| ------------- | ---
| Branch? | 3.4
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| License | MIT
This is slimmed down version of: https://github.com/symfony/symfony/pull/28269 _(many of the fixes there are already part of 3.4)_
Does:
- Adds test coverage for Predis with RedisCluster
- Removes usage of key versioning when on RedisCluster, besides performance aspect of that it simplifies / aligning clear() handling across all clients
- reuse doDelete() from clear to make sure we call singular del calls when in predis cluster
Commits
-------
dacf17962b
[Cache] Fix perf when using RedisCluster by reducing roundtrips to the servers
This commit is contained in:
commit
26a92b1bbc
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Cache\Tests\Adapter;
|
||||||
|
|
||||||
|
class PredisRedisClusterAdapterTest extends AbstractRedisAdapterTest
|
||||||
|
{
|
||||||
|
public static function setupBeforeClass()
|
||||||
|
{
|
||||||
|
if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) {
|
||||||
|
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
|
||||||
|
}
|
||||||
|
self::$redis = new \Predis\Client(explode(' ', $hosts), ['cluster' => 'redis']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function tearDownAfterClass()
|
||||||
|
{
|
||||||
|
self::$redis = null;
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,6 @@
|
|||||||
namespace Symfony\Component\Cache\Traits;
|
namespace Symfony\Component\Cache\Traits;
|
||||||
|
|
||||||
use Predis\Connection\Aggregate\ClusterInterface;
|
use Predis\Connection\Aggregate\ClusterInterface;
|
||||||
use Predis\Connection\Aggregate\PredisCluster;
|
|
||||||
use Predis\Connection\Aggregate\RedisCluster;
|
use Predis\Connection\Aggregate\RedisCluster;
|
||||||
use Predis\Connection\Factory;
|
use Predis\Connection\Factory;
|
||||||
use Predis\Response\Status;
|
use Predis\Response\Status;
|
||||||
@ -48,9 +47,7 @@ trait RedisTrait
|
|||||||
if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
|
if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
|
||||||
throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
|
throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
|
||||||
}
|
}
|
||||||
if ($redisClient instanceof \RedisCluster) {
|
if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\Client && !$redisClient instanceof RedisProxy) {
|
||||||
$this->enableVersioning();
|
|
||||||
} elseif (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \Predis\Client && !$redisClient instanceof RedisProxy) {
|
|
||||||
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, \is_object($redisClient) ? \get_class($redisClient) : \gettype($redisClient)));
|
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, \is_object($redisClient) ? \get_class($redisClient) : \gettype($redisClient)));
|
||||||
}
|
}
|
||||||
$this->redis = $redisClient;
|
$this->redis = $redisClient;
|
||||||
@ -207,9 +204,6 @@ trait RedisTrait
|
|||||||
*/
|
*/
|
||||||
protected function doClear($namespace)
|
protected function doClear($namespace)
|
||||||
{
|
{
|
||||||
// When using a native Redis cluster, clearing the cache is done by versioning in AbstractTrait::clear().
|
|
||||||
// This means old keys are not really removed until they expire and may need garbage collection.
|
|
||||||
|
|
||||||
$cleared = true;
|
$cleared = true;
|
||||||
$hosts = [$this->redis];
|
$hosts = [$this->redis];
|
||||||
$evalArgs = [[$namespace], 0];
|
$evalArgs = [[$namespace], 0];
|
||||||
@ -218,13 +212,11 @@ trait RedisTrait
|
|||||||
$evalArgs = [0, $namespace];
|
$evalArgs = [0, $namespace];
|
||||||
|
|
||||||
$connection = $this->redis->getConnection();
|
$connection = $this->redis->getConnection();
|
||||||
if ($connection instanceof PredisCluster) {
|
if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) {
|
||||||
$hosts = [];
|
$hosts = [];
|
||||||
foreach ($connection as $c) {
|
foreach ($connection as $c) {
|
||||||
$hosts[] = new \Predis\Client($c);
|
$hosts[] = new \Predis\Client($c);
|
||||||
}
|
}
|
||||||
} elseif ($connection instanceof RedisCluster) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} elseif ($this->redis instanceof \RedisArray) {
|
} elseif ($this->redis instanceof \RedisArray) {
|
||||||
$hosts = [];
|
$hosts = [];
|
||||||
@ -232,7 +224,11 @@ trait RedisTrait
|
|||||||
$hosts[] = $this->redis->_instance($host);
|
$hosts[] = $this->redis->_instance($host);
|
||||||
}
|
}
|
||||||
} elseif ($this->redis instanceof \RedisCluster) {
|
} elseif ($this->redis instanceof \RedisCluster) {
|
||||||
return false;
|
$hosts = [];
|
||||||
|
foreach ($this->redis->_masters() as $host) {
|
||||||
|
$hosts[] = $h = new \Redis();
|
||||||
|
$h->connect($host[0], $host[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach ($hosts as $host) {
|
foreach ($hosts as $host) {
|
||||||
if (!isset($namespace[0])) {
|
if (!isset($namespace[0])) {
|
||||||
@ -259,7 +255,7 @@ trait RedisTrait
|
|||||||
$keys = $keys[1];
|
$keys = $keys[1];
|
||||||
}
|
}
|
||||||
if ($keys) {
|
if ($keys) {
|
||||||
$host->del($keys);
|
$this->doDelete($keys);
|
||||||
}
|
}
|
||||||
} while ($cursor = (int) $cursor);
|
} while ($cursor = (int) $cursor);
|
||||||
}
|
}
|
||||||
@ -331,7 +327,16 @@ trait RedisTrait
|
|||||||
{
|
{
|
||||||
$ids = [];
|
$ids = [];
|
||||||
|
|
||||||
if ($this->redis instanceof \Predis\Client && !$this->redis->getConnection() instanceof ClusterInterface) {
|
if ($this->redis instanceof \RedisCluster || ($this->redis instanceof \Predis\Client && $this->redis->getConnection() instanceof RedisCluster)) {
|
||||||
|
// phpredis & predis don't support pipelining with RedisCluster
|
||||||
|
// see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining
|
||||||
|
// see https://github.com/nrk/predis/issues/267#issuecomment-123781423
|
||||||
|
$results = [];
|
||||||
|
foreach ($generator() as $command => $args) {
|
||||||
|
$results[] = \call_user_func_array([$this->redis, $command], $args);
|
||||||
|
$ids[] = $args[0];
|
||||||
|
}
|
||||||
|
} elseif ($this->redis instanceof \Predis\Client) {
|
||||||
$results = $this->redis->pipeline(function ($redis) use ($generator, &$ids) {
|
$results = $this->redis->pipeline(function ($redis) use ($generator, &$ids) {
|
||||||
foreach ($generator() as $command => $args) {
|
foreach ($generator() as $command => $args) {
|
||||||
\call_user_func_array([$redis, $command], $args);
|
\call_user_func_array([$redis, $command], $args);
|
||||||
@ -355,15 +360,6 @@ trait RedisTrait
|
|||||||
foreach ($results as $k => list($h, $c)) {
|
foreach ($results as $k => list($h, $c)) {
|
||||||
$results[$k] = $connections[$h][$c];
|
$results[$k] = $connections[$h][$c];
|
||||||
}
|
}
|
||||||
} elseif ($this->redis instanceof \RedisCluster || ($this->redis instanceof \Predis\Client && $this->redis->getConnection() instanceof ClusterInterface)) {
|
|
||||||
// phpredis & predis don't support pipelining with RedisCluster
|
|
||||||
// see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining
|
|
||||||
// see https://github.com/nrk/predis/issues/267#issuecomment-123781423
|
|
||||||
$results = [];
|
|
||||||
foreach ($generator() as $command => $args) {
|
|
||||||
$results[] = \call_user_func_array([$this->redis, $command], $args);
|
|
||||||
$ids[] = $args[0];
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$this->redis->multi(\Redis::PIPELINE);
|
$this->redis->multi(\Redis::PIPELINE);
|
||||||
foreach ($generator() as $command => $args) {
|
foreach ($generator() as $command => $args) {
|
||||||
|
Reference in New Issue
Block a user