Flask Caching
Caching stores the result of an expensive operation and reuses it for subsequent requests. Instead of querying the database and rendering a template on every request, Flask serves the cached result in milliseconds. Caching dramatically reduces server load and speeds up response times.
The Vending Machine Analogy
Without caching, every customer who wants a drink must wait for a bartender to mix it fresh. With caching, the bartender pre-fills drinks at the start of the hour and serves from a tray. The drinks are ready instantly until the tray expires and needs refilling.
Without cache: With cache: Request → DB query → render Request → cache hit → instant response Request → DB query → render (100ms each) (1ms each) Request → DB query → render (100ms each)
Installing Flask-Caching
pip install flask-cachingSetting Up Flask-Caching
from flask import Flask
from flask_caching import Cache
app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache' # In-memory (development)
app.config['CACHE_DEFAULT_TIMEOUT'] = 300 # 5 minutes default
cache = Cache(app)Cache Backend Options
| Backend | Config | Use When |
|---|---|---|
| SimpleCache | CACHE_TYPE = 'SimpleCache' | Development / single process |
| Redis | CACHE_TYPE = 'RedisCache' | Production / multiple servers |
| Memcached | CACHE_TYPE = 'MemcachedCache' | Production / high traffic |
| FileSystem | CACHE_TYPE = 'FileSystemCache' | Simple file-based persistence |
| NullCache | CACHE_TYPE = 'NullCache' | Testing (caching disabled) |
Caching a View with @cache.cached
@app.route('/products')
@cache.cached(timeout=60) # cache this response for 60 seconds
def product_list():
products = Product.query.all()
return render_template('products.html', products=products)The first request runs the full function and stores the HTML. All subsequent requests within 60 seconds return the stored HTML instantly without hitting the database.
Caching Per-User Pages
By default, @cache.cached stores one response for all users. If the page is user-specific, add a cache key function:
def user_cache_key():
return f'dashboard_{session.get("user_id")}'
@app.route('/dashboard')
@cache.cached(timeout=120, key_prefix=user_cache_key)
def dashboard():
user = User.query.get(session['user_id'])
return render_template('dashboard.html', user=user)Each user gets their own cached version keyed by their ID.
Caching Functions with @cache.memoize
@cache.memoize caches a function's return value based on its arguments. Call the same function with the same arguments again — get the cached result.
@cache.memoize(timeout=300)
def get_product_stats(product_id):
# Expensive database aggregation
product = Product.query.get(product_id)
sales = Sale.query.filter_by(product_id=product_id).count()
revenue = db.session.query(db.func.sum(Sale.amount)) \
.filter_by(product_id=product_id).scalar()
return {'sales': sales, 'revenue': revenue}
@app.route('/product/<int:pid>')
def product_detail(pid):
stats = get_product_stats(pid) # cached per pid
return jsonify(stats)Manual Cache Operations
# Store a value manually
cache.set('homepage_hit_count', 1500, timeout=3600)
# Read a value
count = cache.get('homepage_hit_count')
# Delete a cached item
cache.delete('homepage_hit_count')
# Clear the entire cache
cache.clear()Invalidating Cache After Updates
Cached data becomes stale when the underlying data changes. Invalidate the relevant cache entry after every update:
@app.route('/product/<int:pid>/update', methods=['POST'])
def update_product(pid):
product = Product.query.get_or_404(pid)
product.price = float(request.form.get('price'))
db.session.commit()
# Invalidate the cached stats for this product
cache.delete_memoized(get_product_stats, pid)
return redirect(url_for('product_detail', pid=pid))Redis Configuration for Production
app.config.update({
'CACHE_TYPE': 'RedisCache',
'CACHE_REDIS_HOST': 'localhost',
'CACHE_REDIS_PORT': 6379,
'CACHE_REDIS_DB': 0,
'CACHE_DEFAULT_TIMEOUT': 300
})Summary
Caching stores expensive results and serves them instantly on repeat requests. Use @cache.cached on view functions that return the same HTML for all users. Use @cache.memoize on helper functions where the result depends on arguments. Always invalidate or delete cached entries when the underlying data changes. In production, use Redis as the cache backend so cached data survives server restarts and scales across multiple processes.
