Python caching, batteries included
Production-ready caching for Python with intelligent reliability features and Rust-powered performance.
Warning
Alpha Software β cachekit is under active development. While we've been building and testing for ~6 months, the API is not yet stable and breaking changes may occur between releases. We're committed to making this library rock-solid, but we need your help!
π Found a bug? Please open an issue β even small ones help us improve.
π‘ Something feel off? We want to hear about rough edges, confusing APIs, or missing features.
Your feedback directly shapes the path to 1.0. Cheers!
Simple to use, production-ready out of the box.
from cachekit import cache
@cache
def expensive_function():
return fetch_data()That's it. You get:
| Feature | Description |
|---|---|
| Circuit breaker | Prevents cascading failures |
| Distributed locking | Multi-pod safety |
| Prometheus metrics | Built-in observability |
| MessagePack serialization | Efficient with optional compression |
| Zero-knowledge encryption | Client-side AES-256-GCM |
pip install cachekitOr with uv (recommended):
uv add cachekitcachekit exposes one decorator API over a pluggable backend abstraction. Pick the
backend that fits your infrastructure β they're peers behind the same @cache API:
| Backend | Best for | Select with |
|---|---|---|
| Redis | Self-hosted, full control | REDIS_URL / CACHEKIT_REDIS_URL |
| CachekitIO | Managed, zero-ops (alpha) | CACHEKIT_API_KEY |
| Memcached | High-throughput, existing infra | CACHEKIT_MEMCACHED_SERVERS |
| File / L1-only | Local dev, tests, no external deps | CACHEKIT_FILE_CACHE_DIR / backend=None |
# Run Redis locally or use your existing infrastructure
export REDIS_URL="redis://localhost:6379"from cachekit import cache
@cache # Auto-detects backend (defaults to Redis at localhost)
def expensive_api_call(user_id: int):
return fetch_user_data(user_id)Tip
No Redis? No worries! Use @cache(backend=None) for L1-only in-memory caching, like lru_cache, but with all the bells and whistles.
CachekitIO β Managed SaaS (Alpha)
import os
from cachekit import cache
# Set your CachekitIO API key
# export CACHEKIT_API_KEY="your-api-key" # pragma: allowlist secret
@cache.io() # Uses CachekitIO SaaS backend β no Redis to manage
def expensive_api_call(user_id: int):
return fetch_user_data(user_id)cachekit.io is in closed alpha β request access to get started.
Memcached β Optional
from cachekit import cache
from cachekit.backends.memcached import MemcachedBackend
# pip install cachekit[memcached]
backend = MemcachedBackend() # Defaults to 127.0.0.1:11211
@cache(backend=backend)
def expensive_api_call(user_id: int):
return fetch_user_data(user_id)Requires: pip install cachekit[memcached] or uv add cachekit[memcached]
CachekitIO Cloud (Alpha) Managed caching with zero infrastructure. L1+L2 caching, circuit breaker, and automatic failover β no Redis to manage. cachekit.io is in closed alpha β request access to get started.
cachekit provides preset configurations for different use cases:
# Speed-critical: trading, gaming, real-time
@cache.minimal
def get_price(symbol: str):
return fetch_price(symbol)
# Reliability-critical: payments, APIs
@cache.production
def process_payment(amount):
return payment_gateway.charge(amount)
# Security-critical: PII, medical, financial
@cache.secure
def get_user_profile(user_id: int):
return db.fetch_user(user_id)| Feature | @cache.minimal |
@cache.dev |
@cache.test |
@cache.production |
@cache.secure |
|---|---|---|---|---|---|
| Circuit Breaker | - | β | - | β | β |
| Backpressure | β | β | - | β | β |
| Integrity Checking | - | β | - | β | β π |
| Encryption | - | - | - | - | β Required |
| L1 SWR | - | β | - | β | β |
| L1 Invalidation | - | - | - | β | β |
| L1 Namespace Index | - | - | - | β | β |
| Prometheus Metrics | - | - | - | β | β |
| Tracing | - | β | - | β | β |
| Structured Logging | - | β | - | β | β |
| Use Case | High throughput | Local debugging | Deterministic tests | Production reliability | Compliance/security |
π
@cache.secureforcesintegrity_checking=Trueβ it cannot be overridden.
@cache.io()mirrors@cache.production(full reliability + observability) but routes to the managed CachekitIO SaaS backend instead of Redis.@cache.local()is a separate in-process path backed byObjectCache(raw object references, entry-count LRU, no serialization) β the reliability and encryption features listed above do not apply to it.
Additional Presets: @cache.dev and @cache.test
See the comparison table above for the exact feature set of each preset.
# Development: verbose logging, integrity checks on, Prometheus off
@cache.dev
def debug_expensive_call():
return complex_computation()
# Testing: deterministic, all protections off (no circuit breaker, no backpressure)
@cache.test
def test_cached_function():
return fixed_test_value()βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β @cache Decorator β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Circuit β β Adaptive β β Distributed β β
β β Breaker β β Timeouts β β Locking β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β L1 Cache (In-Memory) β L2 Cache (Pluggable Backend) β
β ~50ns β Redis / CachekitIO / File / β
β β Memcached ~2-50ms β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Rust Core (PyO3) β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β LZ4 β β xxHash3 β β AES-256-GCM β β
β β Compression β β Checksums β β Encryption β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Tip
Building in Rust? The core compression, checksums, and encryption are available as a standalone crate: cachekit-core
- Circuit breaker with graceful degradation
- Connection pooling with thread affinity (+28% throughput)
- Distributed locking prevents cache stampedes
- Pluggable backend abstraction (Redis, CachekitIO, File, Memcached, custom)
Note
All reliability features are enabled by default with @cache.production. Use @cache.minimal to disable them for maximum throughput.
| Serializer | Speed | Use Case |
|---|---|---|
| StandardSerializer | β β β β β | General Python types, NumPy, Pandas |
| OrjsonSerializer | β β β β β | JSON APIs (2-5x faster than stdlib) β requires cachekit[json] |
| ArrowSerializer | β β β β β | Large DataFrames (6-23x faster for 10K+ rows) |
| EncryptionWrapper | β β β β β | Wraps any serializer with AES-256-GCM |
Serializer Examples
from cachekit.serializers import OrjsonSerializer, ArrowSerializer, EncryptionWrapper
# Fast JSON for API responses
@cache.production(serializer=OrjsonSerializer())
def get_api_response(endpoint: str):
return {"status": "success", "data": fetch_api(endpoint)}
# Zero-copy DataFrames for large datasets
@cache(serializer=ArrowSerializer())
def get_large_dataset(date: str):
return pd.read_csv(f"data/{date}.csv")
# Encrypted DataFrames for sensitive data
@cache(serializer=EncryptionWrapper(serializer=ArrowSerializer()))
def get_patient_data(hospital_id: int):
return pd.read_sql("SELECT * FROM patients WHERE hospital_id = ?", conn, params=[hospital_id])Important
All serializers support configurable checksums for corruption detection using xxHash3-64 (8 bytes). Enabled by default in @cache.production and @cache.secure.
Performance Impact (benchmark-proven):
| Data Type | Latency Reduction (disabled) | Size Overhead |
|---|---|---|
| MessagePack (default) | 60-90% | 8 bytes |
| Arrow DataFrames | 35-49% | 8 bytes |
| JSON (orjson) | 37-68% | 8 bytes |
Caution
When handling PII, medical, or financial data, always use @cache.secure to enforce encryption.
cachekit employs comprehensive security tooling:
- Supply Chain Security: cargo-deny for license compliance + RustSec scanning
- Formal Verification: Kani proves correctness of compression, checksums, encryption
- Runtime Analysis: Miri + sanitizers for memory safety
- Fuzzing: Coverage-guided testing with >80% code coverage
- Zero CVEs: Continuous vulnerability scanning
Security Commands
make security-install # Install security tools (one-time)
make security-fast # Run fast checks (< 3 min)Security Tiers:
| Tier | Time | Coverage |
|---|---|---|
| Fast | < 3 min | Vulnerability scanning, license checks, linting |
| Medium | < 15 min | Unsafe code analysis, API stability, Miri subset |
| Deep | < 2 hours | Formal verification, extended fuzzing, full sanitizers |
See SECURITY.md for vulnerability reporting and detailed documentation.
- Per-function statistics -
cache_info()on every decorated function, modelled onfunctools.lru_cache - Prometheus metrics - Recorded by default (your app owns exposition)
- Structured logging - Context-aware with correlation IDs
- Health checks - Comprehensive status endpoints
Every decorated function exposes cache_info(), returning a CacheInfo named tuple with
hit/miss counts, the L1/L2 split, and average backend latency:
@cache()
def get_score(x):
return x ** 2
get_score(2)
get_score(2) # served from cache
info = get_score.cache_info()
# CacheInfo has 9 fields: hits, misses, l1_hits, l2_hits, maxsize,
# currsize, l2_avg_latency_ms, last_operation_at, session_id
assert info.l1_hits + info.l2_hits == info.hits # every hit is L1 or L2maxsize and currsize are always None (the cache lives in an external store, not a
bounded in-process dict); they exist only for lru_cache API parity. See the
API Reference for the full
field reference and a sample stats endpoint, and the
Prometheus Metrics guide for metric names and
exposition setup.
Thread Safety Details
Per-Function Statistics:
- Statistics tracked per function identity (
module.qualname), shared across all calls and across re-decorations of the same function - Thread-safe via RLock (all methods safe for concurrent access)
- Fork-safe: a forked child starts with zeroed counters and its own session ID
from concurrent.futures import ThreadPoolExecutor
@cache()
def expensive_func(x):
return x ** 2
# All threads share same stats
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(expensive_func, range(100)))
info = expensive_func.cache_info()
# CacheInfo(hits=..., misses=..., l1_hits=..., l2_hits=...,
# maxsize=None, currsize=None, l2_avg_latency_ms=...,
# last_operation_at=..., session_id=...)| Guide | Description |
|---|---|
| Comparison Guide | How cachekit compares to lru_cache, aiocache, cachetools |
| Getting Started | Progressive tutorial from basics to advanced |
| API Reference | Complete API documentation |
| Feature | Description |
|---|---|
| Serializer Guide | ArrowSerializer vs StandardSerializer benchmarks |
| Circuit Breaker | Prevent cascading failures |
| Distributed Locking | Cache stampede prevention |
| Prometheus Metrics | Built-in observability |
| Zero-Knowledge Encryption | Client-side security |
# Redis Connection (priority: CACHEKIT_REDIS_URL > REDIS_URL)
CACHEKIT_REDIS_URL="redis://localhost:6379" # Primary (preferred)
REDIS_URL="redis://localhost:6379" # Fallback
# CachekitIO SaaS Backend (closed alpha β request access at cachekit.io)
CACHEKIT_API_KEY="your-api-key" # Required for @cache.io() # pragma: allowlist secret
CACHEKIT_API_URL="https://api.cachekit.io" # Default SaaS endpoint
# Memcached Backend (optional: pip install cachekit[memcached])
CACHEKIT_MEMCACHED_SERVERS='["mc1:11211", "mc2:11211"]' # Default: 127.0.0.1:11211
CACHEKIT_MEMCACHED_CONNECT_TIMEOUT=2.0 # Default: 2.0 seconds
CACHEKIT_MEMCACHED_TIMEOUT=1.0 # Default: 1.0 seconds
CACHEKIT_MEMCACHED_KEY_PREFIX="myapp:" # Default: "" (none)
# Optional Configuration
CACHEKIT_DEFAULT_TTL=3600
CACHEKIT_MAX_VALUE_SIZE=104857600
CACHEKIT_ARROW_COMPRESSION=zstdNote
If both CACHEKIT_REDIS_URL and REDIS_URL are set, CACHEKIT_REDIS_URL takes precedence.
git clone https://github.com/cachekit-io/cachekit-py.git
cd cachekit-py
uv sync && make install
make quick-check # format + lint + critical testsSee CONTRIBUTING.md for full development guidelines.
| Component | Version |
|---|---|
| Python | 3.10+ |
MIT License - see LICENSE for details.