or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async.mdindex.mdrate-limit-items.mdstorage.mdstrategies.mdutilities.md

storage.mddocs/

0

# Storage Backends

1

2

Storage backends handle the persistence and distribution of rate limit data across different storage systems. The choice of storage backend determines whether rate limiting is local to a single process or distributed across multiple instances.

3

4

## Capabilities

5

6

### Storage Factory

7

8

Factory function for creating storage instances from URI strings, supporting both synchronous and asynchronous storage backends.

9

10

```python { .api }

11

def storage_from_string(storage_string: str, **options: float | str | bool) -> Storage:

12

"""

13

Create storage instance from URI string.

14

15

Supports various storage schemes including memory, Redis, Memcached,

16

MongoDB, and etcd. Can create both sync and async storage instances

17

based on URI scheme.

18

19

Args:

20

storage_string: URI like "redis://localhost:6379" or "memory://"

21

options: Additional options passed to storage constructor

22

23

Returns:

24

Storage instance matching the URI scheme

25

26

Raises:

27

ConfigurationError: If storage scheme is unknown or unsupported

28

29

Examples:

30

memory = storage_from_string("memory://")

31

redis = storage_from_string("redis://localhost:6379")

32

async_redis = storage_from_string("async+redis://localhost:6379")

33

"""

34

```

35

36

### Base Storage Classes

37

38

Abstract base classes defining the storage interface and common functionality.

39

40

```python { .api }

41

from abc import ABC, abstractmethod

42

from limits.util import LazyDependency

43

44

class Storage(LazyDependency, ABC):

45

"""

46

Base class for all storage backends.

47

48

Provides common interface for storing and retrieving rate limit data.

49

Extends LazyDependency to handle optional dependencies for specific backends.

50

"""

51

52

STORAGE_SCHEME: list[str] | None # Supported URI schemes

53

54

def __init__(self, uri: str | None = None, wrap_exceptions: bool = False, **options):

55

"""

56

Initialize storage backend.

57

58

Args:

59

uri: Connection URI for the storage backend

60

wrap_exceptions: Whether to wrap storage exceptions in StorageError

61

options: Additional backend-specific options

62

"""

63

64

@property

65

@abstractmethod

66

def base_exceptions(self) -> type[Exception] | tuple[type[Exception], ...]:

67

"""Base exception types that this storage backend can raise"""

68

69

@abstractmethod

70

def incr(self, key: str, expiry: int, elastic_expiry: bool = False, amount: int = 1) -> int:

71

"""

72

Increment counter for key.

73

74

Args:

75

key: Storage key for the rate limit

76

expiry: Expiration time in seconds

77

elastic_expiry: Whether to use elastic expiry behavior

78

amount: Amount to increment by

79

80

Returns:

81

New counter value after increment

82

"""

83

84

@abstractmethod

85

def get(self, key: str) -> int:

86

"""

87

Get current counter value for key.

88

89

Args:

90

key: Storage key

91

92

Returns:

93

Current counter value, 0 if key doesn't exist

94

"""

95

96

@abstractmethod

97

def get_expiry(self, key: str) -> float:

98

"""

99

Get expiration time for key.

100

101

Args:

102

key: Storage key

103

104

Returns:

105

Expiration timestamp, or 0 if key doesn't exist

106

"""

107

108

@abstractmethod

109

def check(self) -> bool:

110

"""

111

Health check for storage backend.

112

113

Returns:

114

True if storage is accessible and working

115

"""

116

117

@abstractmethod

118

def reset(self) -> int | None:

119

"""Reset/clear all stored data"""

120

121

@abstractmethod

122

def clear(self, key: str) -> None:

123

"""

124

Clear data for specific key.

125

126

Args:

127

key: Storage key to clear

128

"""

129

130

class MovingWindowSupport(ABC):

131

"""Interface for storage backends supporting moving window strategy"""

132

133

@abstractmethod

134

def acquire_entry(self, key: str, limit: int, expiry: int, amount: int = 1) -> bool:

135

"""

136

Acquire entry in moving window.

137

138

Args:

139

key: Storage key

140

limit: Rate limit amount

141

expiry: Window duration in seconds

142

amount: Number of entries to acquire

143

144

Returns:

145

True if entries were acquired, False if limit exceeded

146

"""

147

148

@abstractmethod

149

def get_moving_window(self, key: str, limit: int, expiry: int) -> tuple[float, int]:

150

"""

151

Get current moving window state.

152

153

Args:

154

key: Storage key

155

limit: Rate limit amount

156

expiry: Window duration in seconds

157

158

Returns:

159

Tuple of (window_start_time, current_count)

160

"""

161

162

class SlidingWindowCounterSupport(ABC):

163

"""Interface for storage backends supporting sliding window counter strategy"""

164

165

@abstractmethod

166

def get_sliding_window(self, key: str, expiry: int) -> tuple[int, float, int, float]:

167

"""

168

Get sliding window counter state.

169

170

Args:

171

key: Storage key

172

expiry: Window duration in seconds

173

174

Returns:

175

Tuple of (previous_count, previous_expires_in, current_count, current_expires_in)

176

"""

177

178

@abstractmethod

179

def acquire_sliding_window_entry(self, key: str, limit: int, expiry: int, amount: int) -> bool:

180

"""

181

Acquire entry using sliding window counter.

182

183

Args:

184

key: Storage key

185

limit: Rate limit amount

186

expiry: Window duration in seconds

187

amount: Number of entries to acquire

188

189

Returns:

190

True if entries were acquired, False if limit exceeded

191

"""

192

```

193

194

### Memory Storage

195

196

In-memory storage backend for single-process applications or testing.

197

198

```python { .api }

199

class MemoryStorage(Storage):

200

"""

201

In-memory storage backend.

202

203

Stores rate limit data in process memory. Not suitable for distributed

204

applications but useful for single-process apps, testing, and development.

205

206

Supports all rate limiting strategies including moving window and

207

sliding window counter.

208

"""

209

210

STORAGE_SCHEME = ["memory"]

211

212

def __init__(self):

213

"""Initialize in-memory storage with empty state"""

214

```

215

216

### Redis Storage Backends

217

218

Redis-based storage backends supporting various Redis deployment patterns.

219

220

```python { .api }

221

class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):

222

"""

223

Redis storage backend.

224

225

Uses Redis for distributed rate limiting across multiple application

226

instances. Supports all rate limiting strategies with high performance

227

and reliability.

228

"""

229

230

STORAGE_SCHEME = ["redis", "rediss", "redis+unix"]

231

232

def __init__(self, uri: str, **options):

233

"""

234

Initialize Redis storage.

235

236

Args:

237

uri: Redis connection URI (redis://host:port/db)

238

options: Additional Redis client options

239

"""

240

241

class RedisClusterStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):

242

"""

243

Redis Cluster storage backend.

244

245

Supports Redis Cluster deployments for horizontal scaling and

246

high availability scenarios.

247

"""

248

249

STORAGE_SCHEME = ["redis+cluster"]

250

251

def __init__(self, uri: str, **options):

252

"""

253

Initialize Redis Cluster storage.

254

255

Args:

256

uri: Redis cluster URI (redis+cluster://host:port)

257

options: Additional cluster client options

258

"""

259

260

class RedisSentinelStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):

261

"""

262

Redis Sentinel storage backend.

263

264

Supports Redis Sentinel for automatic failover and high availability.

265

"""

266

267

STORAGE_SCHEME = ["redis+sentinel"]

268

269

def __init__(self, uri: str, **options):

270

"""

271

Initialize Redis Sentinel storage.

272

273

Args:

274

uri: Sentinel URI (redis+sentinel://host:port/service)

275

options: Additional sentinel options

276

"""

277

```

278

279

### Memcached Storage

280

281

Memcached storage backend for distributed caching scenarios.

282

283

```python { .api }

284

class MemcachedStorage(Storage):

285

"""

286

Memcached storage backend.

287

288

Uses Memcached for distributed rate limiting. Supports basic rate limiting

289

strategies but not moving window or sliding window counter due to

290

Memcached's limited data structure support.

291

"""

292

293

STORAGE_SCHEME = ["memcached"]

294

295

def __init__(self, uri: str, **options):

296

"""

297

Initialize Memcached storage.

298

299

Args:

300

uri: Memcached URI (memcached://host:port)

301

options: Additional memcached client options

302

"""

303

```

304

305

### MongoDB Storage

306

307

MongoDB storage backend for document-based persistence.

308

309

```python { .api }

310

class MongoDBStorageBase(Storage):

311

"""Base class for MongoDB storage implementations"""

312

313

class MongoDBStorage(MongoDBStorageBase, MovingWindowSupport, SlidingWindowCounterSupport):

314

"""

315

MongoDB storage backend.

316

317

Uses MongoDB for persistent rate limiting data with support for all

318

rate limiting strategies. Suitable for applications already using

319

MongoDB or requiring persistent rate limit data.

320

"""

321

322

STORAGE_SCHEME = ["mongodb"]

323

324

def __init__(self, uri: str, **options):

325

"""

326

Initialize MongoDB storage.

327

328

Args:

329

uri: MongoDB connection URI (mongodb://host:port/database)

330

options: Additional MongoDB client options

331

"""

332

```

333

334

### Etcd Storage

335

336

Etcd storage backend for distributed key-value storage.

337

338

```python { .api }

339

class EtcdStorage(Storage):

340

"""

341

Etcd storage backend.

342

343

Uses etcd for distributed rate limiting in Kubernetes and other

344

cloud-native environments. Provides consistency guarantees but

345

with higher latency than Redis.

346

"""

347

348

STORAGE_SCHEME = ["etcd"]

349

350

def __init__(self, uri: str, **options):

351

"""

352

Initialize etcd storage.

353

354

Args:

355

uri: Etcd connection URI (etcd://host:port)

356

options: Additional etcd client options

357

"""

358

```

359

360

### Helper Classes

361

362

Helper classes providing common functionality for storage implementations.

363

364

```python { .api }

365

class TimestampedSlidingWindow:

366

"""Helper class for storage that support sliding window counter with timestamp-based keys"""

367

368

@classmethod

369

def sliding_window_keys(cls, key: str, expiry: int, at: float) -> tuple[str, str]:

370

"""

371

Generate keys for previous and current sliding window buckets.

372

373

Args:

374

key: Base key for the rate limit

375

expiry: Window duration in seconds

376

at: Timestamp to generate keys for (usually current time)

377

378

Returns:

379

Tuple of (previous_window_key, current_window_key)

380

381

Example:

382

With key="mykey", expiry=60, at=1738576292.6631825

383

Returns ("mykey/28976271", "mykey/28976270")

384

"""

385

```

386

387

## Usage Examples

388

389

### Storage Selection

390

391

```python

392

from limits.storage import storage_from_string

393

394

# In-memory storage (single process)

395

memory_storage = storage_from_string("memory://")

396

397

# Redis storage (distributed)

398

redis_storage = storage_from_string("redis://localhost:6379")

399

redis_with_db = storage_from_string("redis://localhost:6379/1")

400

redis_with_auth = storage_from_string("redis://:password@localhost:6379")

401

402

# Valkey storage (Redis-compatible)

403

valkey_storage = storage_from_string("valkey://localhost:6379")

404

valkey_with_ssl = storage_from_string("valkeys://localhost:6380")

405

406

# Redis Cluster

407

cluster_storage = storage_from_string("redis+cluster://localhost:7000")

408

409

# Redis Sentinel

410

sentinel_storage = storage_from_string(

411

"redis+sentinel://localhost:26379/mymaster"

412

)

413

414

# Memcached

415

memcached_storage = storage_from_string("memcached://localhost:11211")

416

417

# MongoDB

418

mongodb_storage = storage_from_string("mongodb://localhost:27017/ratelimits")

419

420

# Etcd

421

etcd_storage = storage_from_string("etcd://localhost:2379")

422

```

423

424

### Storage with Options

425

426

```python

427

from limits.storage import storage_from_string

428

429

# Redis with connection options

430

redis_storage = storage_from_string(

431

"redis://localhost:6379",

432

socket_timeout=5,

433

socket_connect_timeout=5,

434

retry_on_timeout=True,

435

health_check_interval=30

436

)

437

438

# MongoDB with collection name

439

mongodb_storage = storage_from_string(

440

"mongodb://localhost:27017/mydb",

441

collection_name="custom_rate_limits"

442

)

443

444

# Memcached with timeout

445

memcached_storage = storage_from_string(

446

"memcached://localhost:11211",

447

timeout=10

448

)

449

```

450

451

### Testing Storage Connectivity

452

453

```python

454

from limits.storage import storage_from_string

455

from limits.errors import ConfigurationError, StorageError

456

457

try:

458

# Create storage instance

459

storage = storage_from_string("redis://localhost:6379")

460

461

# Test connectivity

462

if storage.check():

463

print("Storage is accessible")

464

else:

465

print("Storage connectivity issues")

466

467

except ConfigurationError as e:

468

print(f"Configuration error: {e}")

469

except StorageError as e:

470

print(f"Storage error: {e}")

471

```

472

473

### Working with Storage Directly

474

475

```python

476

from limits.storage import MemoryStorage

477

478

# Create storage instance

479

storage = MemoryStorage()

480

481

# Basic operations

482

key = "user:12345:api_calls"

483

expiry = 3600 # 1 hour

484

485

# Increment counter

486

current_count = storage.incr(key, expiry, amount=1)

487

print(f"Current count: {current_count}")

488

489

# Get current value

490

count = storage.get(key)

491

print(f"Retrieved count: {count}")

492

493

# Get expiry time

494

expire_time = storage.get_expiry(key)

495

print(f"Expires at: {expire_time}")

496

497

# Clear specific key

498

storage.clear(key)

499

500

# Reset all data

501

storage.reset()

502

```

503

504

### Storage Schemes Reference

505

506

```python { .api }

507

# Available storage schemes

508

SUPPORTED_SCHEMES = {

509

"memory": MemoryStorage,

510

"redis": RedisStorage,

511

"rediss": RedisStorage, # Redis with SSL

512

"redis+unix": RedisStorage, # Redis Unix socket

513

"valkey": RedisStorage, # Valkey backend

514

"valkeys": RedisStorage, # Valkey with SSL

515

"valkey+unix": RedisStorage, # Valkey Unix socket

516

"redis+cluster": RedisClusterStorage,

517

"valkey+cluster": RedisClusterStorage, # Valkey cluster

518

"redis+sentinel": RedisSentinelStorage,

519

"valkey+sentinel": RedisSentinelStorage, # Valkey sentinel

520

"memcached": MemcachedStorage,

521

"mongodb": MongoDBStorage,

522

"mongodb+srv": MongoDBStorage, # MongoDB with SRV records

523

"etcd": EtcdStorage,

524

}

525

526

# Async storage schemes (prefix with "async+")

527

ASYNC_SCHEMES = {

528

"async+memory": "limits.aio.storage.MemoryStorage",

529

"async+redis": "limits.aio.storage.RedisStorage",

530

"async+valkey": "limits.aio.storage.RedisStorage", # Valkey async support

531

"async+memcached": "limits.aio.storage.MemcachedStorage",

532

"async+mongodb": "limits.aio.storage.MongoDBStorage",

533

"async+etcd": "limits.aio.storage.EtcdStorage"

534

}

535

```