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-caching

Setting 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

BackendConfigUse When
SimpleCacheCACHE_TYPE = 'SimpleCache'Development / single process
RedisCACHE_TYPE = 'RedisCache'Production / multiple servers
MemcachedCACHE_TYPE = 'MemcachedCache'Production / high traffic
FileSystemCACHE_TYPE = 'FileSystemCache'Simple file-based persistence
NullCacheCACHE_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.

Leave a Comment

Your email address will not be published. Required fields are marked *