Advanced Caching in APIFromAnythingο
This document provides detailed information about the advanced caching features available in APIFromAnything.
Overviewο
APIFromAnything provides a sophisticated caching system that allows you to cache API responses with fine-grained control. The advanced caching features include:
Multiple cache backends (in-memory, Redis, Memcached)
Distributed caching support
Tag-based and dependency-based cache invalidation strategies
Cache control decorators for fine-grained control
Automatic cache key generation based on request parameters
Cache headers management
Cache Backendsο
In-Memory Cacheο
The simplest cache backend that stores cache entries in memory. This is suitable for single-instance applications or development environments.
from apifrom.middleware.cache_advanced import MemoryCacheBackend
cache_backend = MemoryCacheBackend()
Redis Cacheο
A distributed cache backend that uses Redis as the storage engine. This is suitable for multi-instance applications or production environments.
from apifrom.middleware.cache_advanced import RedisCacheBackend
cache_backend = RedisCacheBackend(
url="redis://localhost:6379/0",
prefix="apifrom:" # Optional prefix for Redis keys
)
Memcached Cacheο
A distributed cache backend that uses Memcached as the storage engine. This is suitable for multi-instance applications or production environments.
from apifrom.middleware.cache_advanced import MemcachedCacheBackend
cache_backend = MemcachedCacheBackend(
servers=["localhost:11211"],
prefix="apifrom:" # Optional prefix for Memcached keys
)
Cache Invalidation Strategiesο
Tag-Based Invalidationο
Tag-based invalidation allows you to associate cache entries with one or more tags, and then invalidate all cache entries with a specific tag.
from apifrom.middleware.cache_advanced import TagBasedInvalidation
invalidation_strategy = TagBasedInvalidation(cache_backend)
Dependency-Based Invalidationο
Dependency-based invalidation allows you to associate cache entries with one or more dependencies, and then invalidate all cache entries that depend on a specific dependency.
from apifrom.middleware.cache_advanced import DependencyBasedInvalidation
invalidation_strategy = DependencyBasedInvalidation(cache_backend)
Advanced Cache Middlewareο
The AdvancedCacheMiddleware is the main component that integrates the cache backend and invalidation strategy with your API application.
from apifrom.middleware.cache_advanced import AdvancedCacheMiddleware
cache_middleware = AdvancedCacheMiddleware(
cache_backend=cache_backend,
invalidation_strategy=invalidation_strategy,
ttl=60, # Default TTL of 60 seconds
key_prefix="cache:", # Optional prefix for cache keys
vary_headers=["Accept", "Accept-Language"] # Headers to include in the cache key
)
app.add_middleware(cache_middleware)
Cache Control Decoratorsο
Cache Decoratorο
The @CacheControl.cache decorator allows you to enable caching for a specific endpoint with custom options.
from apifrom.middleware.cache_advanced import CacheControl
@api(app)
@CacheControl.cache(
ttl=60, # Cache for 60 seconds
tags=["posts"], # Associate with tags (for tag-based invalidation)
dependencies=["posts"] # Associate with dependencies (for dependency-based invalidation)
)
async def get_posts() -> List[Dict]:
"""Get all posts."""
return posts_db
No Cache Decoratorο
The @CacheControl.no_cache decorator allows you to disable caching for a specific endpoint.
from apifrom.middleware.cache_advanced import CacheControl
@api(app)
@CacheControl.no_cache
async def get_stats() -> Dict:
"""Get real-time statistics."""
return {"users": 100, "posts": 200, "comments": 300}
Invalidate Decoratorο
The @CacheControl.invalidate decorator allows you to invalidate cache entries after an endpoint is called.
from apifrom.middleware.cache_advanced import CacheControl
@api(app)
@CacheControl.invalidate(["posts", "post:{post_id}"])
async def update_post(post_id: int, title: str = None, content: str = None) -> Dict:
"""Update a post."""
# Update the post in the database
return {"message": "Post updated"}
Advanced Usage Examplesο
Blog API with Dependency-Based Invalidationο
from apifrom import API, api
from apifrom.middleware.cache_advanced import (
AdvancedCacheMiddleware,
RedisCacheBackend,
DependencyBasedInvalidation,
CacheControl
)
from typing import Dict, List
import asyncio
# Create an API instance
app = API(title="Blog API")
# Create a Redis cache backend
try:
cache_backend = RedisCacheBackend(url="redis://localhost:6379/0")
except ImportError:
from apifrom.middleware.cache_advanced import MemoryCacheBackend
cache_backend = MemoryCacheBackend()
# Create a dependency-based invalidation strategy
invalidation_strategy = DependencyBasedInvalidation(cache_backend)
# Add advanced cache middleware
app.add_middleware(
AdvancedCacheMiddleware(
cache_backend=cache_backend,
invalidation_strategy=invalidation_strategy,
ttl=60
)
)
# In-memory database for demonstration
posts_db = [
{"id": 1, "title": "First Post", "content": "This is the first post"},
{"id": 2, "title": "Second Post", "content": "This is the second post"},
]
# API endpoints
@api(app)
@CacheControl.cache(ttl=30, dependencies=["posts"])
async def get_posts() -> List[Dict]:
"""Get all posts."""
return posts_db
@api(app)
@CacheControl.cache(ttl=60, dependencies=["posts", "post:{post_id}"])
async def get_post(post_id: int) -> Dict:
"""Get a post by ID."""
for post in posts_db:
if post["id"] == post_id:
return post
return {"error": "Post not found"}
@api(app)
@CacheControl.invalidate(["posts"])
async def create_post(title: str, content: str) -> Dict:
"""Create a new post."""
post_id = max(post["id"] for post in posts_db) + 1
new_post = {"id": post_id, "title": title, "content": content}
posts_db.append(new_post)
return {"message": "Post created", "post": new_post}
@api(app)
@CacheControl.invalidate(["posts", "post:{post_id}"])
async def update_post(post_id: int, title: str = None, content: str = None) -> Dict:
"""Update a post."""
for post in posts_db:
if post["id"] == post_id:
if title is not None:
post["title"] = title
if content is not None:
post["content"] = content
return {"message": "Post updated", "post": post}
return {"error": "Post not found"}
@api(app)
@CacheControl.invalidate(["posts", "post:{post_id}"])
async def delete_post(post_id: int) -> Dict:
"""Delete a post."""
for i, post in enumerate(posts_db):
if post["id"] == post_id:
del posts_db[i]
return {"message": "Post deleted"}
return {"error": "Post not found"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
E-commerce API with Tag-Based Invalidationο
from apifrom import API, api
from apifrom.middleware.cache_advanced import (
AdvancedCacheMiddleware,
MemcachedCacheBackend,
TagBasedInvalidation,
CacheControl
)
from typing import Dict, List
import asyncio
# Create an API instance
app = API(title="E-commerce API")
# Create a Memcached cache backend
try:
cache_backend = MemcachedCacheBackend(servers=["localhost:11211"])
except ImportError:
from apifrom.middleware.cache_advanced import MemoryCacheBackend
cache_backend = MemoryCacheBackend()
# Create a tag-based invalidation strategy
invalidation_strategy = TagBasedInvalidation(cache_backend)
# Add advanced cache middleware
app.add_middleware(
AdvancedCacheMiddleware(
cache_backend=cache_backend,
invalidation_strategy=invalidation_strategy,
ttl=60
)
)
# In-memory database for demonstration
products_db = [
{"id": 1, "name": "Product 1", "price": 10.0, "category_id": 1},
{"id": 2, "name": "Product 2", "price": 20.0, "category_id": 1},
{"id": 3, "name": "Product 3", "price": 30.0, "category_id": 2},
]
categories_db = [
{"id": 1, "name": "Category 1"},
{"id": 2, "name": "Category 2"},
]
# API endpoints
@api(app)
@CacheControl.cache(ttl=30, tags=["products"])
async def get_products() -> List[Dict]:
"""Get all products."""
return products_db
@api(app)
@CacheControl.cache(ttl=60, tags=["products", "product:{product_id}"])
async def get_product(product_id: int) -> Dict:
"""Get a product by ID."""
for product in products_db:
if product["id"] == product_id:
return product
return {"error": "Product not found"}
@api(app)
@CacheControl.cache(ttl=30, tags=["categories"])
async def get_categories() -> List[Dict]:
"""Get all categories."""
return categories_db
@api(app)
@CacheControl.cache(ttl=60, tags=["categories", "category:{category_id}"])
async def get_category(category_id: int) -> Dict:
"""Get a category by ID."""
for category in categories_db:
if category["id"] == category_id:
return category
return {"error": "Category not found"}
@api(app)
@CacheControl.cache(ttl=30, tags=["products", "category:{category_id}"])
async def get_products_by_category(category_id: int) -> List[Dict]:
"""Get products by category."""
category_products = []
for product in products_db:
if product["category_id"] == category_id:
category_products.append(product)
return category_products
@api(app)
@CacheControl.invalidate(["products", "product:{product_id}"])
async def update_product(product_id: int, name: str = None, price: float = None) -> Dict:
"""Update a product."""
for product in products_db:
if product["id"] == product_id:
if name is not None:
product["name"] = name
if price is not None:
product["price"] = price
return {"message": "Product updated", "product": product}
return {"error": "Product not found"}
@api(app)
@CacheControl.invalidate(["categories", "category:{category_id}"])
async def update_category(category_id: int, name: str = None) -> Dict:
"""Update a category."""
for category in categories_db:
if category["id"] == category_id:
if name is not None:
category["name"] = name
return {"message": "Category updated", "category": category}
return {"error": "Category not found"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
Performance Considerationsο
Cache Key Generationο
The cache key is generated based on the request method, path, query parameters, and vary headers. This ensures that different requests get different cache entries.
Cache Invalidationο
Cache invalidation is a critical aspect of caching. The advanced caching system provides two invalidation strategies:
Tag-based invalidation: Associate cache entries with tags and invalidate all entries with a specific tag.
Dependency-based invalidation: Associate cache entries with dependencies and invalidate all entries that depend on a specific dependency.
Distributed Cachingο
For multi-instance applications, itβs recommended to use a distributed cache backend like Redis or Memcached. This ensures that all instances share the same cache and invalidation events.
Best Practicesο
Use appropriate TTLs: Set appropriate time-to-live (TTL) values for your cache entries based on how frequently the data changes.
Choose the right invalidation strategy: Use tag-based invalidation for simple scenarios and dependency-based invalidation for more complex scenarios.
Be careful with cache keys: Ensure that your cache keys are unique for different requests but consistent for the same request.
Monitor cache performance: Keep an eye on cache hit rates and invalidation events to ensure your caching strategy is effective.
Test cache invalidation: Thoroughly test your cache invalidation logic to ensure that stale data is not served to users.
Consider cache stampede: Implement mechanisms to prevent cache stampede (many concurrent requests trying to rebuild the cache at the same time).
Use vary headers wisely: Include only the headers that affect the response content in the vary headers list.
Conclusionο
The advanced caching system in APIFromAnything provides a powerful and flexible way to improve the performance of your API. By using the appropriate cache backend, invalidation strategy, and cache control decorators, you can achieve significant performance improvements while maintaining data consistency.