Skip to content

Commit 5891ade

Browse files
committed
feat: add node manager for cluster configuration handling
- Introduce `NodeManager` class to manage Typesense cluster nodes - Implement round-robin node selection strategy - Add nearest node prioritization feature - Include node health tracking and periodic health checks - Enhance fault tolerance and load balancing across the cluster
1 parent a15fa56 commit 5891ade

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

src/typesense/node_manager.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""
2+
This module provides functionality for managing nodes in a Typesense cluster configuration.
3+
4+
It contains the NodeManager class, which is responsible for node selection, health checks,
5+
and rotation strategies for load balancing and fault tolerance in a Typesense cluster.
6+
7+
Key features:
8+
- Round-robin node selection
9+
- Nearest node prioritization (if configured)
10+
- Node health tracking and updates
11+
- Periodic health checks based on a configurable interval
12+
13+
Classes:
14+
NodeManager: Manages the nodes in a Typesense cluster configuration.
15+
16+
Dependencies:
17+
- typesense.configuration: Provides Configuration and Node classes
18+
- typesense.logger: Provides logging functionality
19+
20+
Usage:
21+
from typesense.configuration import Configuration
22+
from node_manager import NodeManager
23+
24+
config = Configuration(...)
25+
node_manager = NodeManager(config)
26+
node = node_manager.get_node()
27+
28+
Note: This module is part of the Typesense Python client library and is
29+
used internally by other components of the library.
30+
"""
31+
32+
import copy
33+
import time
34+
35+
from typesense.configuration import Configuration, Node
36+
from typesense.logger import logger
37+
38+
39+
class NodeManager:
40+
"""
41+
Manages the nodes in a Typesense cluster configuration.
42+
43+
This class handles node selection, health checks, and rotation for load balancing
44+
and fault tolerance in a Typesense cluster.
45+
46+
Attributes:
47+
config (Configuration): The configuration object for the Typesense client.
48+
nodes (List[Node]): A copy of the nodes from the configuration.
49+
node_index (int): The index of the current node in the rotation.
50+
"""
51+
52+
def __init__(self, config: Configuration):
53+
"""
54+
Initialize the NodeManager with a given configuration.
55+
56+
Args:
57+
config (Configuration): The configuration object for the Typesense client.
58+
"""
59+
self.config = config
60+
self.nodes = copy.deepcopy(config.nodes)
61+
self.node_index = 0
62+
self._initialize_nodes()
63+
64+
def get_node(self) -> Node:
65+
"""
66+
Get the next available healthy node.
67+
68+
This method implements a round-robin selection strategy, prioritizing the nearest node
69+
if configured, and considering the health status of each node.
70+
71+
Returns:
72+
Node: The selected node for the next operation.
73+
"""
74+
if self.config.nearest_node:
75+
if self.config.nearest_node.healthy or self._is_due_for_health_check(
76+
self.config.nearest_node,
77+
):
78+
return self.config.nearest_node
79+
80+
node_index = 0
81+
while node_index < len(self.nodes):
82+
node_index += 1
83+
node = self.nodes[self.node_index]
84+
self.node_index = (self.node_index + 1) % len(self.nodes)
85+
if node.healthy or self._is_due_for_health_check(node):
86+
return node
87+
88+
logger.debug("No healthy nodes were found. Returning the next node.")
89+
return self.nodes[self.node_index]
90+
91+
def set_node_health(self, node: Node, is_healthy: bool) -> None:
92+
"""
93+
Set the health status of a node and update its last access timestamp.
94+
95+
Args:
96+
node (Node): The node to update.
97+
is_healthy (bool): The health status to set for the node.
98+
"""
99+
node.healthy = is_healthy
100+
node.last_access_ts = int(time.time())
101+
102+
def _is_due_for_health_check(self, node: Node) -> bool:
103+
"""
104+
Check if a node is due for a health check based on the configured interval.
105+
106+
Args:
107+
node (Node): The node to check.
108+
109+
Returns:
110+
bool: True if the node is due for a health check, False otherwise.
111+
"""
112+
current_epoch_ts = int(time.time())
113+
return bool(
114+
(current_epoch_ts - node.last_access_ts)
115+
> self.config.healthcheck_interval_seconds,
116+
)
117+
118+
def _initialize_nodes(self) -> None:
119+
"""
120+
Initialize all nodes as healthy.
121+
122+
This method sets the initial health status of all nodes, including the nearest node
123+
if configured, to healthy.
124+
"""
125+
if self.config.nearest_node:
126+
self.set_node_health(self.config.nearest_node, is_healthy=True)
127+
for node in self.nodes:
128+
self.set_node_health(node, is_healthy=True)

0 commit comments

Comments
 (0)