Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/multi_database.rst → docs/geographic_failover.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Multi-database client (Active-Active)
Client-side geographic failover (Active-Active)
=====================================

The multi-database client allows your application to connect to multiple Redis databases, which are typically replicas of each other.
Expand Down
10 changes: 8 additions & 2 deletions redis/asyncio/multidb/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
class MultiDBClient(AsyncRedisModuleCommands, AsyncCoreCommands):
"""
Client that operates on multiple logical Redis databases.
Should be used in Active-Active database setups.
Should be used in Client-side geographic failover database setups.
"""

def __init__(self, config: MultiDbConfig):
Expand Down Expand Up @@ -299,7 +299,7 @@ async def _check_databases_health(
unhealthy_db = result.database
unhealthy_db.circuit.state = CBState.OPEN

logger.exception(
logger.debug(
"Health check failed, due to exception",
exc_info=result.original_exception,
)
Expand Down Expand Up @@ -337,8 +337,14 @@ def _on_circuit_state_change_callback(
return

if old_state == CBState.CLOSED and new_state == CBState.OPEN:
logger.error(
f"Database {circuit.database} is unreachable. Failover has been initiated."
)
loop.call_later(DEFAULT_GRACE_PERIOD, _half_open_circuit, circuit)

if old_state != CBState.CLOSED and new_state == CBState.CLOSED:
logger.info(f"Database {circuit.database} is reachable again.")

async def aclose(self):
if self.command_executor.active_database:
await self.command_executor.active_database.client.aclose()
Expand Down
3 changes: 3 additions & 0 deletions redis/asyncio/multidb/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ def circuit(self) -> CircuitBreaker:
@circuit.setter
def circuit(self, circuit: CircuitBreaker):
self._cb = circuit

def __repr__(self):
return f"Database(client={self.client}, weight={self.weight})"
14 changes: 13 additions & 1 deletion redis/multidb/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
class MultiDBClient(RedisModuleCommands, CoreCommands):
"""
Client that operates on multiple logical Redis databases.
Should be used in Active-Active database setups.
Should be used in Client-side geographic failover database setups.
"""

def __init__(self, config: MultiDbConfig):
Expand Down Expand Up @@ -276,6 +276,11 @@ def _check_databases_health(self, on_error: Callable[[Exception], None] = None):
unhealthy_db = e.database
unhealthy_db.circuit.state = CBState.OPEN

logger.debug(
"Health check failed, due to exception",
exc_info=e.original_exception,
)

if on_error:
on_error(e.original_exception)
except TimeoutError:
Expand All @@ -291,10 +296,17 @@ def _on_circuit_state_change_callback(
return

if old_state == CBState.CLOSED and new_state == CBState.OPEN:
logger.error(
f"Database {circuit.database} is unreachable. Failover has been initiated."
)

self._bg_scheduler.run_once(
DEFAULT_GRACE_PERIOD, _half_open_circuit, circuit
)

if old_state != CBState.CLOSED and new_state == CBState.CLOSED:
logger.info(f"Database {circuit.database} is reachable again.")

def close(self):
"""
Closes the client and all its resources.
Expand Down
3 changes: 3 additions & 0 deletions redis/multidb/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,6 @@ def circuit(self) -> CircuitBreaker:
@circuit.setter
def circuit(self, circuit: CircuitBreaker):
self._cb = circuit

def __repr__(self):
return f"Database(client={self.client}, weight={self.weight})"