diff --git a/api/app/settings/common.py b/api/app/settings/common.py index 12ea7938146f..e37f16f8c760 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -852,6 +852,12 @@ "DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS", True ) +# Enable reading from replicas in Redis Cluster mode. +# Distributes read traffic to replica nodes (port 6380 on ElastiCache Serverless). +REDIS_CLUSTER_READ_FROM_REPLICAS = env.bool( + "REDIS_CLUSTER_READ_FROM_REPLICAS", default=True +) + CACHES = { "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache", diff --git a/api/core/redis_cluster.py b/api/core/redis_cluster.py index a0dc28a51527..1571a4d9b6d0 100644 --- a/api/core/redis_cluster.py +++ b/api/core/redis_cluster.py @@ -24,6 +24,7 @@ import threading from copy import deepcopy +from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django_redis.client.default import DefaultClient # type: ignore[import-untyped] from django_redis.exceptions import ( # type: ignore[import-untyped] @@ -125,6 +126,9 @@ def get_connection(self, connection_params: dict) -> RedisCluster: # type: igno # Add explicit socket timeout client_cls_kwargs["socket_timeout"] = SOCKET_TIMEOUT client_cls_kwargs["socket_keepalive"] = True + client_cls_kwargs["read_from_replicas"] = ( + settings.REDIS_CLUSTER_READ_FROM_REPLICAS + ) # ... and then build and return the client return RedisCluster(**client_cls_kwargs) # type: ignore[abstract] except Exception as e: diff --git a/api/tests/unit/core/test_redis_cluster.py b/api/tests/unit/core/test_redis_cluster.py index d23e85fc660a..a26d87148b34 100644 --- a/api/tests/unit/core/test_redis_cluster.py +++ b/api/tests/unit/core/test_redis_cluster.py @@ -2,6 +2,7 @@ from django_redis.exceptions import ( # type: ignore[import-untyped] ConnectionInterrupted, ) +from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture from redis.exceptions import RedisClusterException @@ -42,8 +43,10 @@ def test_cluster_connection_factory__connect_cache(mocker: MockerFixture): # ty def test_cluster_connection_factory__get_connection_with_non_conflicting_params( # type: ignore[no-untyped-def] mocker: MockerFixture, + settings: SettingsWrapper, ): # Given + settings.REDIS_CLUSTER_READ_FROM_REPLICAS = False mockRedisCluster = mocker.patch("core.redis_cluster.RedisCluster") connection_factory = ClusterConnectionFactory( options={"REDIS_CLIENT_KWARGS": {"decode_responses": False}} @@ -60,6 +63,7 @@ def test_cluster_connection_factory__get_connection_with_non_conflicting_params( port=6379, socket_keepalive=True, socket_timeout=0.2, + read_from_replicas=False, )