Disk Cache -- Disk and file backed persistent cache.
—
DiskCache provides seamless Django integration through the DjangoCache class, which implements Django's cache backend interface while maintaining all DiskCache features and performance benefits. This allows Django applications to use persistent disk-based caching with minimal configuration changes.
A Django-compatible cache backend that wraps FanoutCache while providing the standard Django cache interface.
class DjangoCache:
def __init__(self, directory, params):
"""
Initialize Django-compatible cache backend.
Args:
directory (str): Cache directory path
params (dict): Django cache configuration parameters:
- SHARDS (int): Number of shards for FanoutCache. Default 8.
- DATABASE_TIMEOUT (float): SQLite timeout. Default 0.010.
- OPTIONS (dict): Additional cache options
"""
@property
def directory(self):
"""Cache directory path."""Standard Django cache methods with DiskCache enhancements.
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, read=False, tag=None, retry=True):
"""
Add key-value pair only if key doesn't exist.
Args:
key (str): Cache key
value: Value to store
timeout (int): Expiration timeout in seconds
version (int, optional): Key version for namespacing
read (bool): Store value as file for reading. Default False.
tag (str, optional): Tag for grouping related items
retry (bool): Retry on timeout. Default True.
Returns:
bool: True if key was added (didn't exist)
"""
def get(self, key, default=None, version=None, read=False, expire_time=False, tag=False, retry=False):
"""
Retrieve value by key.
Args:
key (str): Cache key
default: Default value if key not found
version (int, optional): Key version for namespacing
read (bool): Return file handle instead of value. Default False.
expire_time (bool): Include expiration time in result. Default False.
tag (bool): Include tag in result. Default False.
retry (bool): Retry on timeout. Default False.
Returns:
Value, or tuple with additional info if expire_time/tag requested
"""
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, read=False, tag=None, retry=True):
"""
Store key-value pair.
Args:
key (str): Cache key
value: Value to store
timeout (int): Expiration timeout in seconds
version (int, optional): Key version for namespacing
read (bool): Store value as file for reading. Default False.
tag (str, optional): Tag for grouping related items
retry (bool): Retry on timeout. Default True.
Returns:
bool: True if set succeeded
"""
def delete(self, key, version=None, retry=True):
"""
Delete key from cache.
Args:
key (str): Cache key to delete
version (int, optional): Key version for namespacing
retry (bool): Retry on timeout. Default True.
Returns:
bool: True if key existed and was deleted
"""
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None, retry=True):
"""
Update expiration time for existing key.
Args:
key (str): Cache key
timeout (int): New expiration timeout in seconds
version (int, optional): Key version for namespacing
retry (bool): Retry on timeout. Default True.
Returns:
bool: True if key existed and was touched
"""
def incr(self, key, delta=1, version=None, default=None, retry=True):
"""
Atomically increment numeric value.
Args:
key (str): Cache key
delta (int): Amount to increment. Default 1.
version (int, optional): Key version for namespacing
default (int, optional): Default value if key doesn't exist
retry (bool): Retry on timeout. Default True.
Returns:
New value after increment
Raises:
ValueError: If key doesn't exist and no default provided
"""
def decr(self, key, delta=1, version=None, default=None, retry=True):
"""
Atomically decrement numeric value.
Args:
key (str): Cache key
delta (int): Amount to decrement. Default 1.
version (int, optional): Key version for namespacing
default (int, optional): Default value if key doesn't exist
retry (bool): Retry on timeout. Default True.
Returns:
New value after decrement
Raises:
ValueError: If key doesn't exist and no default provided
"""
def has_key(self, key, version=None):
"""
Check if key exists in cache.
Args:
key (str): Cache key
version (int, optional): Key version for namespacing
Returns:
bool: True if key exists
"""
def clear(self):
"""
Remove all items from cache.
Returns:
None
"""Additional methods that extend Django's cache interface with DiskCache features.
def read(self, key, version=None):
"""
Get file handle for key stored in read mode.
Args:
key (str): Cache key
version (int, optional): Key version for namespacing
Returns:
File handle or None if key not found
"""
def pop(self, key, default=None, version=None, expire_time=False, tag=False, retry=True):
"""
Remove and return value for key.
Args:
key (str): Cache key
default: Default value if key not found
version (int, optional): Key version for namespacing
expire_time (bool): Include expiration time in result. Default False.
tag (bool): Include tag in result. Default False.
retry (bool): Retry on timeout. Default True.
Returns:
Value, or tuple with additional info if expire_time/tag requested
"""Create sub-collections within the Django cache directory.
def cache(self, name):
"""
Return Cache instance in subdirectory.
Args:
name (str): Subdirectory name for Cache
Returns:
Cache: Cache instance in subdirectory
"""
def deque(self, name, maxlen=None):
"""
Return Deque instance in subdirectory.
Args:
name (str): Subdirectory name for Deque
maxlen (int, optional): Maximum length of deque
Returns:
Deque: Deque instance in subdirectory
"""
def index(self, name):
"""
Return Index instance in subdirectory.
Args:
name (str): Subdirectory name for Index
Returns:
Index: Index instance in subdirectory
"""Cache management operations with Django compatibility.
def expire(self):
"""
Remove expired items from cache.
Returns:
int: Number of expired items removed
"""
def stats(self, enable=True, reset=False):
"""
Get cache hit/miss statistics.
Args:
enable (bool): Enable statistics tracking. Default True.
reset (bool): Reset statistics counters. Default False.
Returns:
Tuple of (hits, misses) counters
"""
def create_tag_index(self):
"""Create database index on tag column for faster tag operations."""
def drop_tag_index(self):
"""Drop database index on tag column."""
def evict(self, tag):
"""
Remove all items with specified tag.
Args:
tag (str): Tag to evict
Returns:
int: Number of items evicted
"""
def cull(self):
"""
Remove items according to eviction policy.
Returns:
int: Number of items removed
"""
def close(self, **kwargs):
"""
Close cache connections and cleanup resources.
Args:
**kwargs: Additional arguments (for Django compatibility)
"""Django-compatible memoization decorator.
def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, typed=False, tag=None, ignore=()):
"""
Memoization decorator using this Django cache.
Args:
name (str, optional): Name for memoized function. Default function name.
timeout (int): Expiration timeout in seconds
version (int, optional): Key version for namespacing
typed (bool): Distinguish arguments by type. Default False.
tag (str, optional): Tag for grouping cached results
ignore (tuple): Argument positions/names to ignore in cache key
Returns:
Decorator function
"""Utility methods for Django integration.
def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
"""
Convert Django timeout format to seconds.
Args:
timeout (int): Django timeout value
Returns:
float: Timeout in seconds, or None for no expiration
"""Configure DiskCache as a Django cache backend in your settings.py:
# settings.py
CACHES = {
'default': {
'BACKEND': 'diskcache.djangocache.DjangoCache',
'LOCATION': '/tmp/django_cache',
'SHARDS': 8,
'DATABASE_TIMEOUT': 0.010,
'OPTIONS': {
'size_limit': 2**32, # 4GB
'eviction_policy': 'least-recently-used',
'statistics': 1,
'tag_index': 1,
},
},
'session': {
'BACKEND': 'diskcache.djangocache.DjangoCache',
'LOCATION': '/tmp/django_sessions',
'SHARDS': 4,
'OPTIONS': {
'size_limit': 2**28, # 256MB
'eviction_policy': 'least-recently-stored',
},
},
}# Multiple caches for different purposes
CACHES = {
'default': {
'BACKEND': 'diskcache.djangocache.DjangoCache',
'LOCATION': '/var/cache/django/default',
'SHARDS': 16,
'OPTIONS': {
'size_limit': 2**30, # 1GB
'eviction_policy': 'least-recently-used',
'statistics': 1,
},
},
'sessions': {
'BACKEND': 'diskcache.djangocache.DjangoCache',
'LOCATION': '/var/cache/django/sessions',
'SHARDS': 4,
'OPTIONS': {
'size_limit': 2**28, # 256MB
'eviction_policy': 'least-recently-stored',
},
},
'database': {
'BACKEND': 'diskcache.djangocache.DjangoCache',
'LOCATION': '/var/cache/django/database',
'SHARDS': 32,
'OPTIONS': {
'size_limit': 2**32, # 4GB
'eviction_policy': 'least-frequently-used',
'statistics': 1,
'tag_index': 1,
},
},
}from django.core.cache import cache
# Basic cache operations
cache.set('user_count', 1250, timeout=300) # 5 minutes
user_count = cache.get('user_count', default=0)
# Add only if doesn't exist
cache.add('unique_visitor', True, timeout=86400) # 24 hours
# Atomic operations
cache.set('page_views', 1000)
cache.incr('page_views') # Now 1001
cache.decr('page_views', 10) # Now 991
# Delete operations
cache.delete('temp_data')
cache.clear() # Remove all itemsfrom django.core.cache import cache
# Use versioning for cache invalidation
cache.set('user_profile', user_data, version=1)
profile = cache.get('user_profile', version=1)
# Invalidate by changing version
cache.set('user_profile', updated_data, version=2)
old_profile = cache.get('user_profile', version=1) # None
new_profile = cache.get('user_profile', version=2) # updated_datafrom django.core.cache import caches
# Access different cache backends
default_cache = caches['default']
session_cache = caches['sessions']
db_cache = caches['database']
# Use each for different purposes
default_cache.set('site_config', config_data)
session_cache.set('user_session', session_data, timeout=1800)
db_cache.set('query_result', query_data, timeout=3600)from django.core.cache import cache
# Use tags for grouped invalidation
cache.set('post_1', post_data, tag='posts', timeout=3600)
cache.set('post_2', other_post, tag='posts', timeout=3600)
cache.set('user_1', user_data, tag='users', timeout=1800)
# Evict all posts
cache.evict('posts') # Removes post_1 and post_2, keeps user_1
# File-based storage for large items
with open('large_file.pdf', 'rb') as f:
file_data = f.read()
cache.set('large_document', file_data, read=True)
# Get file handle instead of loading into memory
file_handle = cache.read('large_document')
if file_handle:
content = file_handle.read()
file_handle.close()
# Statistics
hits, misses = cache.stats()
print(f"Cache hit ratio: {hits/(hits+misses):.2%}")from django.core.cache import cache
# Create specialized data structures
user_cache = cache.cache('users')
task_queue = cache.deque('tasks')
search_index = cache.index('search')
# Use sub-collections independently
user_cache.set('user:123', user_data)
task_queue.append({'task': 'send_email', 'user_id': 123})
search_index['keyword:python'] = ['doc1', 'doc2', 'doc3']
# These don't interfere with main cache
cache.set('global_setting', 'value') # Different namespace# Use DiskCache as Django session backend
# In settings.py:
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'sessions'
# Sessions will be stored in DiskCache automatically
# with all the persistence and performance benefitsfrom django.views.decorators.cache import cache_page
from django.core.cache import cache
from django.template.loader import render_to_string
# Cache entire view for 15 minutes
@cache_page(60 * 15, cache='default')
def expensive_view(request):
# Expensive computation
data = perform_complex_calculation()
return render(request, 'template.html', {'data': data})
# Manual template caching
def cached_template_fragment(context_data):
cache_key = f"template_fragment_{hash(str(context_data))}"
rendered = cache.get(cache_key)
if rendered is None:
rendered = render_to_string('fragment.html', context_data)
cache.set(cache_key, rendered, timeout=3600, tag='templates')
return rendered
# Invalidate template cache when data changes
def invalidate_template_cache():
cache.evict('templates') # Remove all cached templatesfrom django.core.cache import cache
from django.db import models
from django.utils.hashlib import md5_constructor
class CachedQueryManager(models.Manager):
def cached_filter(self, timeout=3600, **kwargs):
# Create cache key from query parameters
key_data = str(sorted(kwargs.items()))
cache_key = f"query_{self.model._meta.label}_{md5_constructor(key_data.encode()).hexdigest()}"
# Try cache first
result = cache.get(cache_key)
if result is None:
result = list(self.filter(**kwargs))
cache.set(cache_key, result, timeout=timeout, tag=f'model_{self.model._meta.label}')
return result
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
published = models.BooleanField(default=False)
objects = CachedQueryManager()
# Usage
published_articles = Article.objects.cached_filter(published=True, timeout=1800)
# Invalidate when models change
def invalidate_article_cache():
cache.evict('model_myapp.Article')from django.core.cache import cache
# Memoize expensive functions
@cache.memoize(timeout=3600, tag='calculations')
def expensive_calculation(x, y, z):
# Complex computation
result = perform_expensive_operation(x, y, z)
return result
# Memoize with versioning
@cache.memoize(name='user_permissions', timeout=1800, version=1)
def get_user_permissions(user_id):
# Database queries to get permissions
return User.objects.get(id=user_id).get_all_permissions()
# Use in views
def user_dashboard(request):
permissions = get_user_permissions(request.user.id)
calculation_result = expensive_calculation(1, 2, 3)
return render(request, 'dashboard.html', {
'permissions': permissions,
'result': calculation_result
})
# Invalidate memoized functions
def invalidate_calculations():
cache.evict('calculations')# Use consistent key naming
CACHE_KEY_PATTERNS = {
'user_profile': 'user:profile:{user_id}',
'article_list': 'articles:list:{category}:{page}',
'search_results': 'search:{query_hash}',
}
def get_cache_key(pattern, **kwargs):
return CACHE_KEY_PATTERNS[pattern].format(**kwargs)
# Usage
cache_key = get_cache_key('user_profile', user_id=123)
cache.set(cache_key, user_data)# Different timeouts for different data types
CACHE_TIMEOUTS = {
'static_content': 86400, # 24 hours
'user_sessions': 1800, # 30 minutes
'api_responses': 300, # 5 minutes
'database_queries': 3600, # 1 hour
'template_fragments': 7200, # 2 hours
}
cache.set('config', data, timeout=CACHE_TIMEOUTS['static_content'])from django.core.cache import cache
import logging
def safe_cache_get(key, default=None):
try:
return cache.get(key, default)
except Exception as e:
logging.error(f"Cache get error for key {key}: {e}")
return default
def safe_cache_set(key, value, timeout=300):
try:
return cache.set(key, value, timeout=timeout)
except Exception as e:
logging.error(f"Cache set error for key {key}: {e}")
return False# Management command for cache monitoring
from django.core.management.base import BaseCommand
from django.core.cache import cache
class Command(BaseCommand):
help = 'Monitor cache statistics'
def handle(self, *args, **options):
hits, misses = cache.stats()
total = hits + misses
if total > 0:
hit_ratio = hits / total
self.stdout.write(f"Cache Statistics:")
self.stdout.write(f" Hits: {hits}")
self.stdout.write(f" Misses: {misses}")
self.stdout.write(f" Hit Ratio: {hit_ratio:.2%}")
# Cleanup expired items
expired = cache.expire()
self.stdout.write(f"Expired {expired} items")
# Show cache size
volume = cache.volume() if hasattr(cache, 'volume') else 'Unknown'
self.stdout.write(f"Cache Size: {volume} bytes")Install with Tessl CLI
npx tessl i tessl/pypi-diskcache