or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client.mdconnections.mdexceptions.mdindex.mdpipeline.mdpubsub.md

exceptions.mddocs/

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

```