I started up a Redis cluster in AWS, with two nodes: the primary, and a replica. The cluster has two entrypoints: primary entrypoint, and reader entrypoint
In my Symfony app I'm using the primary entrypoint in the .env configuration
REDIS_HOST=xxx-redis.yyy.ng.0001.zone.cache.amazonaws.com
REDIS_PORT=6379
and cache configuration under the framework.cache in the cache.yaml like this:
app: cache.adapter.redis
default_redis_provider: redis://%env(REDIS_HOST)%:%env(int:REDIS_PORT)%
The app is working well and cache is being used, but I noticed that only the primary node is being used, the replica does not register any hit.
I googled a lot for configuration examples on how to add the read only entrypoint to the symfony redis configuration, but found nothing.
So, I hope somebody has had the same issue and figured out the solution. I think that maybe doing a custom cache adapter, or something like that maybe?
To utilize replication, you can provide multiple URL's inside Redis DNS as stated inside this documentation. Use AWS provided main Primary and Reader endpoints, not the ones from individual nodes. So you only need 2 URL's, no
default_redis_provider: 'redis:?host[xxx-redis.yyy.ng.0001.zone.cache.amazonaws.com]&host[xxx-redis.yyy-ro.ng.0001.zone.cache.amazonaws.com]'
Please note that there is no //
after redis:
. All this information can be read and debugged inside /vendor/symfony/cache/Traits/RedisTrait.php
createConnection method.
Now the fun part if using Predis package:
It requires adding ?role=master
and optionally ?role=slave
into host URL.
So your YAML config would look like this:
default_redis_provider: 'redis:?host[xxx-redis.amazonaws.com%3Frole%3Dmaster]&host[xxx-redis-ro.amazonaws.com%3Frole%3Dslave]'
%
host[xxx-redis.amazonaws.com][role]=master
>-
there will be white spaces added to parameters:default_redis_provider: >-
?host[xxx-redis.amazonaws.com][role]=master
&host[xxx-redis-ro.amazonaws.com][role]=slave
becomes ?host[xxx-redis.amazonaws.com][role]=master &host[xxx-redis-ro.amazonaws.com][role]=slave
For this I have made my own impelementation of RedisAdapter:
framework:
cache:
prefix_seed: '%env(ENVIRONMENT)%'
pools:
external.cache:
adapter: cache.adapter.redis_tag_aware
provider: App\Component\Cache\Adapter\RedisAdapter
services:
App\Component\Cache\Adapter\RedisAdapter:
factory: [null, 'createReplicaConnection']
arguments:
$primaryHost: '%app.cache.redis.host.primary%'
$readerHost: '%app.cache.redis.host.reader%'
use Predis\Client;
use Symfony\Component\Cache\Adapter\RedisAdapter as BaseRedisAdapter;
class RedisAdapter extends BaseRedisAdapter
{
public static function createReplicaConnection(
string|array $primaryHost,
null|string|array $readerHost = null,
array $options = [],
): \Redis|\RedisArray|\RedisCluster|ClientInterface|Relay {
$data = [
'host' => [],
'class' => Client::class,
'replication' => 'predis',
];
$primaryHost = array_merge(
['role' => 'master', 'alias' => 'primary'],
is_string($primaryHost) ? ['host' => $primaryHost] : $primaryHost,
);
$data['host'][$primaryHost['host']] = $primaryHost;
if ($readerHost !== null) {
$readerHost = array_merge(
['role' => 'slave', 'alias' => 'reader'],
is_string($readerHost) ? ['host' => $readerHost] : $readerHost,
);
$data['host'][$readerHost['host']] = $readerHost;
}
return static::createConnection('redis:?' . http_build_query($data), $options);
}
Symfony 7.1 has issues with Predis: https://github.com/symfony/symfony/issues/49238