0
# Exception Handling
1
2
Comprehensive exception classes for handling cluster-specific errors, redirections, failure scenarios, and constraint violations. These exceptions provide detailed information for implementing proper error handling and retry logic in cluster applications.
3
4
## Capabilities
5
6
### Base Cluster Exceptions
7
8
Core exception classes for general cluster error handling.
9
10
```python { .api }
11
class RedisClusterException(Exception):
12
"""
13
Base exception for all cluster-specific errors.
14
15
Used for:
16
- Cluster constraint violations
17
- Pipeline limitations
18
- Configuration errors
19
- General cluster operation failures
20
"""
21
22
class RedisClusterError(Exception):
23
"""
24
General cluster operation error.
25
26
Used for:
27
- Command execution failures
28
- Cluster state inconsistencies
29
- Operational errors not covered by specific exceptions
30
"""
31
```
32
33
### Cluster State Exceptions
34
35
Exceptions related to cluster availability and operational state.
36
37
```python { .api }
38
class ClusterDownException(Exception):
39
"""
40
Raised when cluster reports it's not operational.
41
42
Indicates:
43
- Cluster is in CLUSTER_STATE_FAIL
44
- Not enough master nodes available
45
- Cluster cannot serve requests
46
"""
47
48
class ClusterDownError(ClusterError, ResponseError):
49
"""
50
Specific cluster down error with server response details.
51
52
Attributes:
53
- message (str): Server response message
54
"""
55
56
def __init__(self, resp):
57
"""
58
Initialize with server response.
59
60
Parameters:
61
- resp (str): Server response message
62
"""
63
```
64
65
### Slot Constraint Exceptions
66
67
Exceptions for hash slot and key distribution violations.
68
69
```python { .api }
70
class ClusterCrossSlotError(ResponseError):
71
"""
72
Keys in request don't hash to the same slot.
73
74
Raised when:
75
- Multi-key operations span different slots
76
- Commands require same-slot keys but don't match
77
- Pipeline operations violate slot constraints
78
79
Attributes:
80
- message (str): "Keys in request don't hash to the same slot"
81
"""
82
83
class SlotNotCoveredError(RedisClusterException):
84
"""
85
Requested slot is not covered by any node.
86
87
Indicates:
88
- Cluster topology incomplete
89
- Slot migration in progress
90
- Node failures affecting slot coverage
91
92
Recovery: Drop current node layout and reconnect
93
"""
94
```
95
96
### Redirection Exceptions
97
98
Exceptions for cluster slot migration and redirection handling.
99
100
```python { .api }
101
class AskError(ResponseError):
102
"""
103
Temporary redirection error during slot migration.
104
105
Occurs when:
106
- Source node is MIGRATING slot to destination node
107
- Key may be on either source or destination
108
- Client should ASK destination then retry command
109
110
Attributes:
111
- slot_id (int): Hash slot being migrated
112
- host (str): Destination host for redirection
113
- port (int): Destination port for redirection
114
- node_addr (tuple): Tuple of (host, port)
115
- message (str): Server response message
116
"""
117
118
def __init__(self, resp):
119
"""
120
Parse redirection information from server response.
121
122
Parameters:
123
- resp (str): Server response "SLOT_ID HOST:PORT"
124
"""
125
126
class MovedError(AskError):
127
"""
128
Permanent redirection error - slot has permanently moved.
129
130
Occurs when:
131
- Slot has been permanently reassigned to different node
132
- Client should update slot mapping
133
- Future commands for this slot go to new node
134
135
Inherits all attributes from AskError:
136
- slot_id, host, port, node_addr, message
137
"""
138
139
class TryAgainError(ResponseError):
140
"""
141
Temporary error - operation should be retried.
142
143
Occurs when:
144
- Cluster is reconfiguring
145
- Temporary resource constraints
146
- Node is busy with internal operations
147
148
Recommended action: Wait briefly then retry
149
"""
150
```
151
152
### Node Failure Exceptions
153
154
Exceptions for master node failures and availability issues.
155
156
```python { .api }
157
class MasterDownError(ClusterDownError):
158
"""
159
Master node is down or unavailable.
160
161
Occurs when:
162
- Master node has failed
163
- Master is unreachable
164
- Failover in progress
165
166
Recovery actions:
167
- Wait for automatic failover
168
- Retry with updated cluster topology
169
- Use replica if available for reads
170
"""
171
```
172
173
### Configuration Exceptions
174
175
Exceptions for cluster setup and configuration issues.
176
177
```python { .api }
178
class RedisClusterConfigError(Exception):
179
"""
180
Cluster configuration error.
181
182
Occurs when:
183
- Invalid startup nodes
184
- Misconfigured cluster parameters
185
- Connection setup failures
186
"""
187
188
class SlotNotCoveredError(RedisClusterException):
189
"""
190
Requested slot is not covered by any cluster node.
191
192
Occurs when:
193
- Cluster topology is incomplete or inconsistent
194
- Hash slot is not assigned to any node
195
- Cluster is in inconsistent state during reconfiguration
196
- Node failures have left slots uncovered
197
198
Recovery action:
199
- Drop current node layout and attempt to reconnect
200
- Refresh cluster topology and slot mappings
201
- Ensure cluster has proper slot coverage before retrying
202
"""
203
```
204
205
## Error Handling Patterns
206
207
### Retry Logic
208
209
Implement proper retry logic for different exception types.
210
211
```python { .api }
212
# Exceptions that typically benefit from retries
213
RETRIABLE_ERRORS = (
214
MovedError, # Update slot mapping and retry
215
AskError, # Send ASK then retry
216
TryAgainError, # Wait briefly and retry
217
MasterDownError, # Wait for failover and retry
218
ConnectionError, # Reconnect and retry
219
TimeoutError # Retry with timeout
220
)
221
222
# Exceptions that usually don't benefit from retries
223
NON_RETRIABLE_ERRORS = (
224
ClusterCrossSlotError, # Fix key distribution
225
RedisClusterException, # Fix application logic
226
SlotNotCoveredError # Reinitialize cluster
227
)
228
```
229
230
### Redirection Handling
231
232
Handle cluster redirections properly for slot migrations.
233
234
```python { .api }
235
def handle_ask_error(client, error, command, *args):
236
"""
237
Handle ASK redirection during slot migration.
238
239
Parameters:
240
- client: Redis cluster client
241
- error (AskError): ASK error with redirection info
242
- command (str): Original command name
243
- *args: Original command arguments
244
245
Returns:
246
Any: Command result from destination node
247
"""
248
249
def handle_moved_error(client, error, command, *args):
250
"""
251
Handle MOVED redirection for permanent slot moves.
252
253
Parameters:
254
- client: Redis cluster client
255
- error (MovedError): MOVED error with new node info
256
- command (str): Original command name
257
- *args: Original command arguments
258
259
Returns:
260
Any: Command result after slot mapping update
261
"""
262
```
263
264
## Usage Examples
265
266
### Basic Exception Handling
267
268
```python
269
from rediscluster import RedisCluster
270
from rediscluster import (
271
RedisClusterException, ClusterDownError, MovedError,
272
AskError, ClusterCrossSlotError
273
)
274
275
rc = RedisCluster(startup_nodes=[{"host": "127.0.0.1", "port": "7000"}])
276
277
try:
278
# This might fail if keys are in different slots
279
result = rc.mget(["key1", "key2", "key3"])
280
281
except ClusterCrossSlotError:
282
print("Keys span multiple slots - using individual gets")
283
results = []
284
for key in ["key1", "key2", "key3"]:
285
try:
286
results.append(rc.get(key))
287
except Exception as e:
288
print(f"Failed to get {key}: {e}")
289
results.append(None)
290
291
except ClusterDownError as e:
292
print(f"Cluster is down: {e.message}")
293
# Wait and retry or use fallback
294
295
except RedisClusterException as e:
296
print(f"Cluster error: {e}")
297
# Handle cluster-specific issues
298
```
299
300
### Redirection Error Handling
301
302
```python
303
import time
304
from rediscluster import RedisCluster
305
from rediscluster import MovedError, AskError, TryAgainError
306
307
def execute_with_redirections(rc, command, *args, max_redirections=5):
308
"""Execute command with automatic redirection handling."""
309
redirections = 0
310
311
while redirections < max_redirections:
312
try:
313
return rc.execute_command(command, *args)
314
315
except MovedError as e:
316
print(f"MOVED: slot {e.slot_id} -> {e.host}:{e.port}")
317
# Client automatically updates slot mapping
318
redirections += 1
319
320
except AskError as e:
321
print(f"ASK: slot {e.slot_id} -> {e.host}:{e.port}")
322
# Client handles ASK redirection automatically
323
redirections += 1
324
325
except TryAgainError:
326
print("TRY_AGAIN - waiting before retry")
327
time.sleep(0.1) # Brief wait
328
redirections += 1
329
330
raise Exception(f"Too many redirections ({max_redirections})")
331
332
# Usage
333
try:
334
result = execute_with_redirections(rc, "GET", "some_key")
335
print(f"Result: {result}")
336
except Exception as e:
337
print(f"Failed after redirections: {e}")
338
```
339
340
### Comprehensive Error Handling
341
342
```python
343
import logging
344
import time
345
from rediscluster import RedisCluster
346
from rediscluster import *
347
from redis.exceptions import ConnectionError, TimeoutError
348
349
def robust_cluster_operation(rc, operation_func, *args, **kwargs):
350
"""
351
Execute cluster operation with comprehensive error handling.
352
353
Parameters:
354
- rc: RedisCluster instance
355
- operation_func: Function to execute (e.g., rc.get, rc.set)
356
- *args, **kwargs: Arguments for operation_func
357
358
Returns:
359
Any: Operation result or None if all retries failed
360
"""
361
max_retries = 3
362
retry_delay = 0.5
363
364
for attempt in range(max_retries):
365
try:
366
return operation_func(*args, **kwargs)
367
368
except ClusterCrossSlotError:
369
logging.error("Keys span multiple slots - cannot execute as single operation")
370
return None
371
372
except (MovedError, AskError) as e:
373
logging.info(f"Redirection: {type(e).__name__} - slot {e.slot_id} -> {e.host}:{e.port}")
374
# Client handles redirection automatically, retry
375
time.sleep(retry_delay)
376
377
except TryAgainError:
378
logging.info("TRY_AGAIN received - cluster busy")
379
time.sleep(retry_delay)
380
381
except (ClusterDownError, MasterDownError) as e:
382
logging.warning(f"Cluster/Master down: {e.message}")
383
time.sleep(retry_delay * 2) # Longer wait for cluster issues
384
385
except (ConnectionError, TimeoutError) as e:
386
logging.warning(f"Connection issue: {e}")
387
time.sleep(retry_delay)
388
389
except SlotNotCoveredError:
390
logging.error("Slot not covered - cluster topology issue")
391
# Force cluster reinitialize
392
rc.connection_pool.reset()
393
time.sleep(retry_delay)
394
395
except RedisClusterException as e:
396
logging.error(f"Cluster constraint violation: {e}")
397
return None # Don't retry constraint violations
398
399
retry_delay *= 1.5 # Exponential backoff
400
401
logging.error(f"Operation failed after {max_retries} attempts")
402
return None
403
404
# Usage examples
405
rc = RedisCluster(startup_nodes=[{"host": "127.0.0.1", "port": "7000"}])
406
407
# Robust single operations
408
value = robust_cluster_operation(rc, rc.get, "some_key")
409
success = robust_cluster_operation(rc, rc.set, "some_key", "some_value")
410
411
# Robust multi-key operations (handles cross-slot errors)
412
values = robust_cluster_operation(rc, rc.mget, ["key1", "key2", "key3"])
413
if values is None:
414
# Fall back to individual gets
415
values = []
416
for key in ["key1", "key2", "key3"]:
417
val = robust_cluster_operation(rc, rc.get, key)
418
values.append(val)
419
```
420
421
### Pipeline Error Handling
422
423
```python
424
from rediscluster import RedisCluster, ClusterPipeline
425
from rediscluster import RedisClusterException
426
427
def safe_pipeline_execution(rc, commands):
428
"""
429
Execute pipeline commands with error handling.
430
431
Parameters:
432
- rc: RedisCluster instance
433
- commands: List of (command, args) tuples
434
435
Returns:
436
List: Results or error information for each command
437
"""
438
pipe = rc.pipeline()
439
440
try:
441
# Queue all commands
442
for command, args in commands:
443
getattr(pipe, command)(*args)
444
445
# Execute pipeline
446
results = pipe.execute(raise_on_error=False)
447
448
# Check for individual command errors
449
processed_results = []
450
for i, result in enumerate(results):
451
if isinstance(result, Exception):
452
command, args = commands[i]
453
logging.error(f"Pipeline command {command}({args}) failed: {result}")
454
processed_results.append({"error": str(result)})
455
else:
456
processed_results.append({"result": result})
457
458
return processed_results
459
460
except RedisClusterException as e:
461
logging.error(f"Pipeline constraint violation: {e}")
462
return [{"error": "Pipeline blocked by cluster constraints"}] * len(commands)
463
464
finally:
465
pipe.reset()
466
467
# Usage
468
commands = [
469
("set", ["key1", "value1"]),
470
("set", ["key2", "value2"]),
471
("get", ["key1"]),
472
("incr", ["counter"])
473
]
474
475
results = safe_pipeline_execution(rc, commands)
476
for i, result in enumerate(results):
477
command, args = commands[i]
478
if "error" in result:
479
print(f"Command {command}({args}) failed: {result['error']}")
480
else:
481
print(f"Command {command}({args}) result: {result['result']}")
482
```