config.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from dataclasses import dataclass, field
  2. from enum import Enum
  3. from typing import List, Optional, Type, Union
  4. import pybreaker
  5. from redis import ConnectionPool, Redis, RedisCluster
  6. from redis.asyncio.multidb.healthcheck import (
  7. DEFAULT_HEALTH_CHECK_DELAY,
  8. DEFAULT_HEALTH_CHECK_INTERVAL,
  9. DEFAULT_HEALTH_CHECK_POLICY,
  10. DEFAULT_HEALTH_CHECK_PROBES,
  11. DEFAULT_HEALTH_CHECK_TIMEOUT,
  12. HealthCheck,
  13. HealthCheckPolicies,
  14. PingHealthCheck,
  15. )
  16. from redis.backoff import ExponentialWithJitterBackoff, NoBackoff
  17. from redis.data_structure import WeightedList
  18. from redis.event import EventDispatcher, EventDispatcherInterface
  19. from redis.maint_notifications import MaintNotificationsConfig
  20. from redis.multidb.circuit import (
  21. DEFAULT_GRACE_PERIOD,
  22. CircuitBreaker,
  23. PBCircuitBreakerAdapter,
  24. )
  25. from redis.multidb.database import Database, Databases
  26. from redis.multidb.failover import (
  27. DEFAULT_FAILOVER_ATTEMPTS,
  28. DEFAULT_FAILOVER_DELAY,
  29. FailoverStrategy,
  30. WeightBasedFailoverStrategy,
  31. )
  32. from redis.multidb.failure_detector import (
  33. DEFAULT_FAILURE_RATE_THRESHOLD,
  34. DEFAULT_FAILURES_DETECTION_WINDOW,
  35. DEFAULT_MIN_NUM_FAILURES,
  36. CommandFailureDetector,
  37. FailureDetector,
  38. )
  39. from redis.retry import Retry
  40. DEFAULT_AUTO_FALLBACK_INTERVAL = 120
  41. class InitialHealthCheck(Enum):
  42. ALL_AVAILABLE = "all_available"
  43. MAJORITY_AVAILABLE = "majority_available"
  44. ONE_AVAILABLE = "one_available"
  45. def default_event_dispatcher() -> EventDispatcherInterface:
  46. return EventDispatcher()
  47. @dataclass
  48. class DatabaseConfig:
  49. """
  50. Dataclass representing the configuration for a database connection.
  51. This class is used to store configuration settings for a database connection,
  52. including client options, connection sourcing details, circuit breaker settings,
  53. and cluster-specific properties. It provides a structure for defining these
  54. attributes and allows for the creation of customized configurations for various
  55. database setups.
  56. Attributes:
  57. weight (float): Weight of the database to define the active one.
  58. client_kwargs (dict): Additional parameters for the database client connection.
  59. from_url (Optional[str]): Redis URL way of connecting to the database.
  60. from_pool (Optional[ConnectionPool]): A pre-configured connection pool to use.
  61. circuit (Optional[CircuitBreaker]): Custom circuit breaker implementation.
  62. grace_period (float): Grace period after which we need to check if the circuit could be closed again.
  63. health_check_url (Optional[str]): URL for health checks. Cluster FQDN is typically used
  64. on public Redis Enterprise endpoints.
  65. Methods:
  66. default_circuit_breaker:
  67. Generates and returns a default CircuitBreaker instance adapted for use.
  68. """
  69. weight: float = 1.0
  70. client_kwargs: dict = field(default_factory=dict)
  71. from_url: Optional[str] = None
  72. from_pool: Optional[ConnectionPool] = None
  73. circuit: Optional[CircuitBreaker] = None
  74. grace_period: float = DEFAULT_GRACE_PERIOD
  75. health_check_url: Optional[str] = None
  76. def default_circuit_breaker(self) -> CircuitBreaker:
  77. circuit_breaker = pybreaker.CircuitBreaker(reset_timeout=self.grace_period)
  78. return PBCircuitBreakerAdapter(circuit_breaker)
  79. @dataclass
  80. class MultiDbConfig:
  81. """
  82. Configuration class for managing multiple database connections in a resilient and fail-safe manner.
  83. Attributes:
  84. databases_config: A list of database configurations.
  85. client_class: The client class used to manage database connections.
  86. command_retry: Retry strategy for executing database commands.
  87. failure_detectors: Optional list of additional failure detectors for monitoring database failures.
  88. min_num_failures: Minimal count of failures required for failover
  89. failure_rate_threshold: Percentage of failures required for failover
  90. failures_detection_window: Time interval for tracking database failures.
  91. health_checks: Optional list of additional health checks performed on databases.
  92. health_check_interval: Time interval for executing health checks.
  93. health_check_probes: Number of attempts to evaluate the health of a database.
  94. health_check_delay: Delay between health check attempts.
  95. health_check_timeout: Timeout for the full health check operation (including all probes).
  96. health_check_policy: Policy for determining database health based on health checks.
  97. failover_strategy: Optional strategy for handling database failover scenarios.
  98. failover_attempts: Number of retries allowed for failover operations.
  99. failover_delay: Delay between failover attempts.
  100. auto_fallback_interval: Time interval to trigger automatic fallback.
  101. event_dispatcher: Interface for dispatching events related to database operations.
  102. initial_health_check_policy: Defines the policy used to determine whether the databases setup is
  103. healthy during the initial health check.
  104. Methods:
  105. databases:
  106. Retrieves a collection of database clients managed by weighted configurations.
  107. Initializes database clients based on the provided configuration and removes
  108. redundant retry objects for lower-level clients to rely on global retry logic.
  109. default_failure_detectors:
  110. Returns the default list of failure detectors used to monitor database failures.
  111. default_health_checks:
  112. Returns the default list of health checks used to monitor database health
  113. with specific retry and backoff strategies.
  114. default_failover_strategy:
  115. Provides the default failover strategy used for handling failover scenarios
  116. with defined retry and backoff configurations.
  117. """
  118. databases_config: List[DatabaseConfig]
  119. client_class: Type[Union[Redis, RedisCluster]] = Redis
  120. command_retry: Retry = Retry(
  121. backoff=ExponentialWithJitterBackoff(base=1, cap=10), retries=3
  122. )
  123. failure_detectors: Optional[List[FailureDetector]] = None
  124. min_num_failures: int = DEFAULT_MIN_NUM_FAILURES
  125. failure_rate_threshold: float = DEFAULT_FAILURE_RATE_THRESHOLD
  126. failures_detection_window: float = DEFAULT_FAILURES_DETECTION_WINDOW
  127. health_checks: Optional[List[HealthCheck]] = None
  128. health_check_interval: float = DEFAULT_HEALTH_CHECK_INTERVAL
  129. health_check_probes: int = DEFAULT_HEALTH_CHECK_PROBES
  130. health_check_delay: float = DEFAULT_HEALTH_CHECK_DELAY
  131. health_check_timeout: float = DEFAULT_HEALTH_CHECK_TIMEOUT
  132. health_check_policy: HealthCheckPolicies = DEFAULT_HEALTH_CHECK_POLICY
  133. failover_strategy: Optional[FailoverStrategy] = None
  134. failover_attempts: int = DEFAULT_FAILOVER_ATTEMPTS
  135. failover_delay: float = DEFAULT_FAILOVER_DELAY
  136. auto_fallback_interval: float = DEFAULT_AUTO_FALLBACK_INTERVAL
  137. event_dispatcher: EventDispatcherInterface = field(
  138. default_factory=default_event_dispatcher
  139. )
  140. initial_health_check_policy: InitialHealthCheck = InitialHealthCheck.ALL_AVAILABLE
  141. def databases(self) -> Databases:
  142. databases = WeightedList()
  143. for database_config in self.databases_config:
  144. # The retry object is not used in the lower level clients, so we can safely remove it.
  145. # We rely on command_retry in terms of global retries.
  146. database_config.client_kwargs["retry"] = Retry(
  147. retries=0, backoff=NoBackoff()
  148. )
  149. # Maintenance notifications are disabled by default in underlying clients,
  150. # but user can override this by providing their own config.
  151. if "maint_notifications_config" not in database_config.client_kwargs:
  152. database_config.client_kwargs["maint_notifications_config"] = (
  153. MaintNotificationsConfig(enabled=False)
  154. )
  155. if database_config.from_url:
  156. client = self.client_class.from_url(
  157. database_config.from_url, **database_config.client_kwargs
  158. )
  159. elif database_config.from_pool:
  160. database_config.from_pool.set_retry(
  161. Retry(retries=0, backoff=NoBackoff())
  162. )
  163. client = self.client_class.from_pool(
  164. connection_pool=database_config.from_pool
  165. )
  166. else:
  167. client = self.client_class(**database_config.client_kwargs)
  168. circuit = (
  169. database_config.default_circuit_breaker()
  170. if database_config.circuit is None
  171. else database_config.circuit
  172. )
  173. databases.add(
  174. Database(
  175. client=client,
  176. circuit=circuit,
  177. weight=database_config.weight,
  178. health_check_url=database_config.health_check_url,
  179. ),
  180. database_config.weight,
  181. )
  182. return databases
  183. def default_failure_detectors(self) -> List[FailureDetector]:
  184. return [
  185. CommandFailureDetector(
  186. min_num_failures=self.min_num_failures,
  187. failure_rate_threshold=self.failure_rate_threshold,
  188. failure_detection_window=self.failures_detection_window,
  189. ),
  190. ]
  191. def default_health_checks(self) -> List[HealthCheck]:
  192. return [
  193. PingHealthCheck(
  194. health_check_probes=self.health_check_probes,
  195. health_check_delay=self.health_check_delay,
  196. health_check_timeout=self.health_check_timeout,
  197. ),
  198. ]
  199. def default_failover_strategy(self) -> FailoverStrategy:
  200. return WeightBasedFailoverStrategy()