RAM-first three-tier cache with swappable backends
Project description
TierCache
RAM-first three-tier cache for Python. Designed to keep your SSD/HDD out of the hot path.
pip install tiercache
How it works
Every request walks down the tier chain until a hit is found:
GET request
│
├─ Hot cache (RAM, 2GB, 4h TTL) ──── HIT → serve, reset TTL
│ MISS ↓
├─ Cold cache (RAM, 10GB, 24h TTL) ──── HIT → promote to hot → serve
│ MISS ↓
└─ Dry cache (Disk / S3 / MongoDB) ─── HIT → promote to hot → serve
MISS → return None (fetch from origin)
SET request
└─ Writes to hot only (zero disk I/O)
│
└─ When hot evicts or expires → auto-demote to dry (failsafe, background)
Both hot and cold live entirely in RAM. Dry is only hit on a true cache miss. After a server restart, the first GET recovers each item from dry back into hot.
Installation
# Base (RAM + local filesystem + SQLite tracking)
pip install tiercache
# With Memcached backends (multi-process / multi-server)
pip install "tiercache[memcached]"
# With Redis tracking
pip install "tiercache[redis]"
# With S3 dry cache
pip install "tiercache[s3]"
# With MongoDB
pip install "tiercache[mongodb]"
# With PostgreSQL tracking
pip install "tiercache[postgres]"
# Everything
pip install "tiercache[all]"
Quick start
From a config file
from tiercache import CacheManager
cache = CacheManager.from_config("tiercache.yaml")
# Async (FastAPI, aiohttp, Sanic)
value = await cache.get("my-key")
await cache.set("my-key", data)
# Sync (Flask, Django)
value = cache.get_sync("my-key")
cache.set_sync("my-key", data)
In code
from tiercache import CacheManager
from tiercache.backends.ram import RamBackend
from tiercache.backends.dry.local import LocalBackend
from tiercache.tracking.sqlite import SQLiteTracking
cache = CacheManager(
hot=RamBackend(ttl_seconds=14400, max_size_bytes=2 * 1024**3),
cold=RamBackend(ttl_seconds=86400, max_size_bytes=10 * 1024**3),
dry=LocalBackend(base_path="/var/cache/myapp/dry", max_size_bytes=100 * 1024**3),
tracking=SQLiteTracking(path="/var/cache/myapp/index.db"),
)
Configuration
# tiercache.yaml
hot_cache:
backend: ram # ram | memcached
ttl_hours: 4
max_size_gb: 2
cold_cache:
backend: ram # ram | memcached
ttl_hours: 24
max_size_gb: 10
dry_cache:
backend: local # local | s3 | mongodb
max_size_gb: 100
path: /var/cache/myapp/dry
tracking:
backend: sqlite # sqlite | redis | postgres | mongodb
# Optional: TTL rules by tag
ttl_rules:
- tag: { type: thumbnail }
hot_ttl_hours: 1
cold_ttl_hours: 6
- tag: { type: raw }
hot_ttl_hours: 8
cold_ttl_hours: 48
Memcached (multi-process / multi-server)
hot_cache:
backend: memcached
ttl_hours: 4
max_size_gb: 2
cold_cache:
backend: memcached
ttl_hours: 24
max_size_gb: 10
memcached:
host: localhost
port: 11211
S3 dry cache
dry_cache:
backend: s3
s3:
endpoint_url: https://s3.amazonaws.com # or MinIO, Cloudflare R2, etc.
bucket: my-cache-bucket
access_key: ...
secret_key: ...
MongoDB dry cache + tracking
dry_cache:
backend: mongodb
tracking:
backend: mongodb
mongodb:
uri: mongodb://localhost:27017
database: tiercache
Redis tracking
tracking:
backend: redis
redis:
host: localhost
port: 6379
db: 0
API
# Fetch a value (returns None on miss)
value = await cache.get("key")
# Store a value using tier default TTL
await cache.set("key", data)
# Override TTL for this key only
await cache.set("key", data, ttl_hours=2)
# Tag-based TTL (matched against ttl_rules in config)
await cache.set("key", data, tags={"type": "thumbnail"})
# Delete from all tiers
await cache.delete("key")
# Flush a specific tier or all
await cache.flush(tier="hot") # hot | cold | dry | all
# Hit/miss stats + tier sizes
stats = await cache.stats()
# {
# "hot_hits": 120, "cold_hits": 30, "dry_hits": 5, "misses": 2,
# "hot_size_bytes": 1048576, "cold_size_bytes": 0, "dry_size_bytes": 4096
# }
# Sync equivalents (Flask, Django)
cache.get_sync("key")
cache.set_sync("key", data, ttl_hours=2, tags={"type": "thumbnail"})
cache.delete_sync("key")
cache.flush_sync(tier="hot")
cache.stats_sync()
# Always close on shutdown
await cache.close()
TTL priority (highest wins)
| Priority | Example |
|---|---|
| 1. Per-key override | cache.set("k", v, ttl_hours=1) |
| 2. Tag rule | cache.set("k", v, tags={"type": "thumbnail"}) → matched in config |
| 3. Tier default | hot_cache.ttl_hours in yaml |
| 4. Global default | hot: 4h, cold: 24h, dry: no expiry |
Backends
| Tier | Backend | Notes |
|---|---|---|
| Hot / Cold | ram |
In-process, single server |
| Hot / Cold | memcached |
Shared pool, multi-process/server |
| Dry | local |
Local filesystem, SSD/HDD |
| Dry | s3 |
AWS S3, MinIO, Cloudflare R2 |
| Dry | mongodb |
GridFS + native TTL indexes |
| Tracking | sqlite |
Zero deps, single machine |
| Tracking | redis |
In-memory, fast, recommended |
| Tracking | postgres |
Production relational |
| Tracking | mongodb |
Flexible schema, TTL indexes |
Example HTTP app (FastAPI)
pip install fastapi uvicorn
# Single process (RAM)
uvicorn example.app:app --port 8989
# Multi-process (Memcached — shared cache across all workers)
SMARTCACHE_CONFIG=example/config_memcached.yaml \
uvicorn example.app:app --workers 4 --port 8989
# Store an image
curl -X PUT "http://localhost:8989/cache/photo.png?tag_type=thumbnail" \
-H "Content-Type: image/png" \
--data-binary @photo.png
# Fetch it (opens directly in browser)
curl "http://localhost:8989/cache/photo.png" -o out.png
# Stats
curl "http://localhost:8989/stats"
Why not just use Redis for everything?
Redis is great but it is a network service — every cache hit is a round trip.
TierCache's RAM backend (ram) stores values directly in the Python process
memory, making hot-path lookups microsecond-range with zero network overhead.
Use memcached when you need shared cache across multiple processes or servers.
Use redis for tracking metadata (tiny footprint, fast, persistent).
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tiercache-0.1.9.tar.gz.
File metadata
- Download URL: tiercache-0.1.9.tar.gz
- Upload date:
- Size: 22.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
84817457bd1644376531ad95ac1f6e6b5d4f17b632f627a9e0f0e1111d0f5722
|
|
| MD5 |
d21de5217af3a08c922f409073ab20fb
|
|
| BLAKE2b-256 |
38fa86dced55d3c051dfac76317d65be70fa49362f1b7d4640f0339f2b5abd90
|
File details
Details for the file tiercache-0.1.9-py3-none-any.whl.
File metadata
- Download URL: tiercache-0.1.9-py3-none-any.whl
- Upload date:
- Size: 23.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54c18857f3b134bd4c481d6108a35c1aba9ec2f0c770e378e8d36e85e2db2c84
|
|
| MD5 |
d5e75e4d28483d2a05aabc2704ad27cb
|
|
| BLAKE2b-256 |
7534f90975158c68afdc40d1cfaedee7b69049e02b198faad440669c6bb068a7
|