Redis Caching Implementation
Distributed caching strategy with Redis for high-performance data access
Overview
The Shirinzad E-Commerce Platform uses Redis-based distributed caching for high-performance data access and scalability. This guide covers cache architecture, configuration, 30+ cache keys, TTL strategies, and best practices.
Benefits: Redis caching reduces database load by 60-80%, improves response times by 50-90%, and enables horizontal scaling across multiple application servers.
Cache Architecture
Cache Layers
graph TB
subgraph Application[Application Layer]
AppService[Application Services]
CacheService[CacheService]
InvalidationService[CacheInvalidationService]
WarmingService[CacheWarmingService]
end
subgraph Redis[Redis Server]
Cache[(Distributed Cache)]
Keys[Cache Keys]
TTL[TTL Management]
end
subgraph Database[Database Layer]
DB[(SQL Server)]
end
AppService --> CacheService
AppService --> InvalidationService
WarmingService --> CacheService
CacheService --> Cache
InvalidationService --> Cache
Cache --> Keys
Keys --> TTL
CacheService -.Fallback.-> DB
WarmingService -.Load Data.-> DB
style CacheService fill:#4CAF50
style Cache fill:#FF9800
style DB fill:#2196F3
Key Components
- CacheService - Core caching operations (Get, Set, GetOrCreate, Remove)
- CacheInvalidationService - Invalidate related caches on data updates
- CacheWarmingService - Preload frequently accessed data on startup
- Redis Server - Distributed cache storage with automatic expiration
Redis Configuration
1. Redis Server Setup
Windows Installation
# Download from: https://github.com/microsoftarchive/redis/releases
# Extract and run redis-server.exe
# Or use Docker:
docker run -d -p 6379:6379 --name redis redis:latest
Linux Installation
# Install Redis
sudo apt-get update
sudo apt-get install redis-server
# Start Redis
sudo systemctl start redis
sudo systemctl enable redis
# Or use Docker:
docker run -d -p 6379:6379 --name redis redis:latest
2. Application Configuration
appsettings.json
{
"Redis": {
"Configuration": "localhost:6379",
"InstanceName": "ShirinzadShop:"
},
"AbpDistributedCache": {
"GlobalCacheEntryOptions": {
"SlidingExpiration": "00:30:00"
}
}
}
Production Configuration (appsettings.Production.json)
{
"Redis": {
"Configuration": "your-redis-server:6379,password=YourPassword,ssl=True,abortConnect=False",
"InstanceName": "ShirinzadShop:Prod:"
}
}
3. Module Configuration
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
// Configure Redis distributed cache
context.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = configuration["Redis:Configuration"];
options.InstanceName = configuration["Redis:InstanceName"];
});
}
Cache Key Structure (30+ Keys)
Naming Convention
All cache keys follow the pattern: {Prefix}:{Type}:{Identifier}:{SubKey}
Prefix: ShirinzadShop: (configured in InstanceName)
Product Cache Keys
| Cache Key | Description | TTL |
|---|---|---|
product:{guid} |
Single product by ID | 2 hours |
product:details:{guid} |
Product with full details (reviews, variants) | 2 hours |
product:slug:{slug} |
Product by URL slug | 2 hours |
product:featured |
Featured products list | 1 hour |
product:top-selling:{start}:{end}:{count} |
Top selling products in date range | 1 hour |
product:low-stock |
Low stock products | 30 minutes |
product:new-arrivals:{count} |
Recently added products | 1 hour |
product:by-category:{categoryId} |
Products in specific category | 2 hours |
product:by-brand:{brandId} |
Products by brand | 2 hours |
Category Cache Keys
| Cache Key | Description | TTL |
|---|---|---|
category:{guid} |
Single category by ID | 6 hours |
category:tree |
Full category hierarchy | 6 hours |
category:list |
All categories (flat list) | 6 hours |
category:slug:{slug} |
Category by URL slug | 6 hours |
category:children:{parentId} |
Child categories of parent | 6 hours |
category:breadcrumb:{categoryId} |
Category breadcrumb trail | 6 hours |
Brand Cache Keys
| Cache Key | Description | TTL |
|---|---|---|
brand:{guid} |
Single brand by ID | 6 hours |
brand:list |
All brands | 6 hours |
brand:featured |
Featured brands | 6 hours |
Dashboard Cache Keys
| Cache Key | Description | TTL |
|---|---|---|
dashboard:statistics |
Dashboard statistics (sales, orders, revenue) | 5 minutes |
dashboard:recent-orders |
Recent orders list | 5 minutes |
dashboard:sales-chart:{period} |
Sales chart data (daily, weekly, monthly) | 15 minutes |
Report Cache Keys
| Cache Key | Description | TTL |
|---|---|---|
report:sales:{start}:{end}:{period} |
Sales report for date range | 1 hour |
report:product-performance:{start}:{end}:{period} |
Product performance report | 1 hour |
report:customer-statistics |
Customer statistics report | 1 hour |
Search Cache Keys
| Cache Key | Description | TTL |
|---|---|---|
search:result:{query}:{filters} |
Search results with filters | 30 minutes |
search:facets:{query} |
Search facets (categories, brands, prices) | 30 minutes |
Configuration Cache Keys
| Cache Key | Description | TTL |
|---|---|---|
config:site-settings |
Site configuration settings | 12 hours |
config:payment-methods |
Available payment methods | 12 hours |
config:shipping-methods |
Available shipping methods | 12 hours |
Cart Cache Keys (Guest Users)
| Cache Key | Description | TTL |
|---|---|---|
cart:session:{sessionId} |
Guest user shopping cart | 24 hours |
TTL (Time-To-Live) Strategy
TTL Guidelines
| Cache Type | TTL | Reason |
|---|---|---|
| Product Details | 2 hours | Medium volatility (price, stock changes) |
| Categories | 6 hours | Low volatility (rarely change) |
| Brands | 6 hours | Low volatility |
| Site Configuration | 12 hours | Very low volatility |
| Featured Products | 1 hour | Needs freshness for marketing |
| Dashboard Stats | 5 minutes | Real-time requirements |
| Search Results | 30 minutes | Medium freshness requirement |
| Low Stock Products | 30 minutes | Inventory critical |
| Cart (Guest) | 24 hours | Session-based |
Important: Adjust TTL values based on your business requirements and data update frequency.
Usage Examples
1. Basic Cache Operations
Get or Create Pattern (Recommended)
public class ProductAppService
{
private readonly CacheService _cacheService;
public async Task<ProductDto> GetAsync(Guid id)
{
var cacheKey = CacheKeys.GetProductDetailsKey(id);
return await _cacheService.GetOrCreateAsync(
cacheKey,
async () => await LoadProductFromDatabaseAsync(id),
TimeSpan.FromHours(2)
);
}
}
Manual Get/Set
public async Task<List<Product>> GetFeaturedProductsAsync()
{
var cached = await _cacheService.GetAsync<List<Product>>(
CacheKeys.FeaturedProductsKey
);
if (cached != null)
{
return cached;
}
var products = await _productRepository.GetListAsync(
p => p.IsFeatured && p.IsActive
);
await _cacheService.SetAsync(
CacheKeys.FeaturedProductsKey,
products,
TimeSpan.FromHours(1)
);
return products;
}
2. Cache Invalidation
public class ProductAppService
{
private readonly CacheInvalidationService _cacheInvalidation;
public async Task<ProductDto> UpdateAsync(Guid id, UpdateProductDto input)
{
var product = await _productRepository.GetAsync(id);
// Update product...
await _productRepository.UpdateAsync(product);
// Invalidate all related caches
await _cacheInvalidation.InvalidateProductCacheAsync(id);
return ObjectMapper.Map<Product, ProductDto>(product);
}
}
CacheInvalidationService Implementation
public class CacheInvalidationService
{
private readonly IDistributedCache _cache;
public async Task InvalidateProductCacheAsync(Guid productId)
{
// Remove specific product caches
await _cache.RemoveAsync($"product:{productId}");
await _cache.RemoveAsync($"product:details:{productId}");
// Remove list caches that might contain this product
await _cache.RemoveAsync("product:featured");
await _cache.RemoveAsync("product:low-stock");
// Remove category and brand caches if needed
// Implementation depends on business logic
}
public async Task InvalidateCategoryCacheAsync(Guid categoryId)
{
await _cache.RemoveAsync($"category:{categoryId}");
await _cache.RemoveAsync("category:tree");
await _cache.RemoveAsync("category:list");
}
public async Task InvalidateDashboardCacheAsync()
{
await _cache.RemoveAsync("dashboard:statistics");
await _cache.RemoveAsync("dashboard:recent-orders");
}
}
3. Cache Warming
On Application Startup
public class YourApplicationModule : AbpModule
{
public override async Task OnApplicationInitializationAsync(
ApplicationInitializationContext context)
{
var cacheWarming = context.ServiceProvider
.GetRequiredService<CacheWarmingService>();
// Warm all critical caches
await cacheWarming.WarmAllCachesAsync();
}
}
CacheWarmingService Implementation
public class CacheWarmingService
{
private readonly CacheService _cacheService;
private readonly IProductRepository _productRepository;
private readonly ICategoryRepository _categoryRepository;
public async Task WarmAllCachesAsync()
{
await WarmProductCachesAsync();
await WarmCategoryCachesAsync();
await WarmConfigurationCachesAsync();
}
private async Task WarmProductCachesAsync()
{
// Featured products
var featuredProducts = await _productRepository.GetListAsync(
p => p.IsFeatured && p.IsActive,
maxResultCount: 10
);
await _cacheService.SetAsync(
"product:featured",
featuredProducts,
TimeSpan.FromHours(1)
);
// Top selling products
var topSelling = await GetTopSellingProductsAsync();
await _cacheService.SetAsync(
"product:top-selling",
topSelling,
TimeSpan.FromHours(1)
);
}
private async Task WarmCategoryCachesAsync()
{
// Category tree
var categoryTree = await BuildCategoryTreeAsync();
await _cacheService.SetAsync(
"category:tree",
categoryTree,
TimeSpan.FromHours(6)
);
}
private async Task WarmConfigurationCachesAsync()
{
// Site settings, payment methods, shipping methods, etc.
}
}
Scheduled Cache Warming (Background Job)
public class CacheWarmingJob : IAsyncBackgroundJob<CacheWarmingArgs>
{
public async Task ExecuteAsync(CacheWarmingArgs args)
{
await _cacheWarmingService.WarmAllCachesAsync();
}
}
// Schedule: Run every 6 hours
await _backgroundJobManager.EnqueueAsync(
new CacheWarmingArgs { WarmProducts = true },
BackgroundJobPriority.Low,
TimeSpan.FromHours(6)
);
Performance Optimization
1. Batch Operations
Bad - Multiple Individual Calls
foreach (var productId in productIds)
{
var product = await _cacheService.GetAsync<Product>(
CacheKeys.GetProductKey(productId)
);
}
Good - Batch Load
var cachedProducts = new List<Product>();
var missingIds = new List<Guid>();
foreach (var productId in productIds)
{
var cached = await _cacheService.GetAsync<Product>(
CacheKeys.GetProductKey(productId)
);
if (cached != null)
cachedProducts.Add(cached);
else
missingIds.Add(productId);
}
if (missingIds.Any())
{
var dbProducts = await _productRepository.GetListAsync(
p => missingIds.Contains(p.Id)
);
foreach (var product in dbProducts)
{
await _cacheService.SetAsync(
CacheKeys.GetProductKey(product.Id),
product,
TimeSpan.FromHours(2)
);
}
cachedProducts.AddRange(dbProducts);
}
2. Cache Aside Pattern
sequenceDiagram
participant App as Application
participant Cache as Redis Cache
participant DB as Database
App->>Cache: Get data
alt Cache Hit
Cache-->>App: Return cached data
else Cache Miss
Cache-->>App: null
App->>DB: Query data
DB-->>App: Return data
App->>Cache: Store data
Cache-->>App: OK
end
public async Task<DashboardStatisticsDto> GetDashboardStatisticsAsync()
{
// Try cache first
var cached = await _cacheService.GetAsync<DashboardStatisticsDto>(
CacheKeys.DashboardStatsKey
);
if (cached != null)
{
return cached;
}
// Cache miss - load from database
var stats = await CalculateDashboardStatisticsAsync();
// Store in cache for next request
await _cacheService.SetAsync(
CacheKeys.DashboardStatsKey,
stats,
TimeSpan.FromMinutes(5)
);
return stats;
}
Monitoring & Debugging
1. Redis CLI Commands
# Connect to Redis
redis-cli
# List all keys
KEYS *
# List keys with pattern
KEYS product:*
# Get key value
GET product:details:guid-here
# Get key TTL
TTL product:details:guid-here
# Delete key
DEL product:details:guid-here
# Flush all cache (DANGER!)
FLUSHALL
# Get cache statistics
INFO stats
# Monitor commands in real-time
MONITOR
2. Monitor Cache Hit Rate
public class CacheMonitoringService
{
private int _hits = 0;
private int _misses = 0;
public async Task<T?> GetWithMonitoringAsync<T>(string key)
where T : class
{
var result = await _cacheService.GetAsync<T>(key);
if (result != null)
Interlocked.Increment(ref _hits);
else
Interlocked.Increment(ref _misses);
return result;
}
public double GetHitRate()
{
var total = _hits + _misses;
return total == 0 ? 0 : (double)_hits / total * 100;
}
public CacheStatistics GetStatistics()
{
return new CacheStatistics
{
Hits = _hits,
Misses = _misses,
HitRate = GetHitRate()
};
}
}
3. Redis Memory Management
Check Memory Usage
redis-cli INFO memory
Set Max Memory Policy
# Set maximum memory
CONFIG SET maxmemory 2gb
# Set eviction policy (allkeys-lru recommended)
CONFIG SET maxmemory-policy allkeys-lru
Available Eviction Policies
- allkeys-lru - Evict least recently used keys (recommended)
- allkeys-lfu - Evict least frequently used keys
- volatile-lru - Evict LRU keys with TTL set
- volatile-ttl - Evict keys with shortest TTL
- noeviction - Return errors when memory limit reached
Best Practices
DO's
- Use cache for read-heavy data - Product catalogs, categories, brands, configuration
- Set appropriate TTL values - Short TTL for volatile data, long TTL for static data
- Invalidate cache on updates - Use CacheInvalidationService, clear related caches
- Warm cache proactively - Featured products, popular categories, dashboard data
- Monitor cache performance - Track hit rate, memory usage, key expiration
- Use consistent naming conventions - Follow the {Prefix}:{Type}:{Identifier} pattern
- Implement fallback logic - Always have database fallback for cache misses
- Use GetOrCreate pattern - Simplifies cache logic and reduces code duplication
DON'Ts
- Don't cache user-specific data globally - Use user-specific keys instead
- Don't cache without expiration - Always set TTL to prevent stale data
- Don't ignore cache invalidation - Stale data causes bugs and poor UX
- Don't cache everything - Cache only frequently accessed data
- Don't forget error handling - Cache failures should not break application
- Don't cache sensitive data - Passwords, credit cards, personal info
- Don't use cache as primary storage - Redis is a cache, not a database
Troubleshooting
Common Issues & Solutions
Issue: High Memory Usage
# Check memory usage
redis-cli INFO memory
# Set max memory policy
CONFIG SET maxmemory 2gb
CONFIG SET maxmemory-policy allkeys-lru
Issue: Cache Not Updating
Check:
- Cache invalidation is called after updates
- TTL is not too long
- Redis server is running
- Connection string is correct
Issue: Slow Performance
Check:
- Redis server resources (CPU, memory)
- Network latency between app and Redis
- Serialization overhead (use efficient serializer)
- Key complexity (avoid overly long keys)
Issue: Connection Timeouts
"Redis": {
"Configuration": "localhost:6379,connectTimeout=5000,syncTimeout=1000,abortConnect=false"
}
Production Checklist
- Redis server configured with persistence (RDB + AOF)
- Redis password authentication enabled
- Redis SSL/TLS for remote connections
- Monitoring and alerting configured
- Backup strategy for Redis data
- Cache key naming convention enforced
- TTL values reviewed and optimized
- Cache warming scheduled
- Cache invalidation tested
- Performance metrics tracked (hit rate, latency)
- Max memory and eviction policy configured
- Connection pooling configured
- High availability setup (Redis Cluster or Sentinel)
Advanced Configuration
Redis Cluster Setup
{
"Redis": {
"Configuration": "node1:6379,node2:6379,node3:6379",
"InstanceName": "ShirinzadShop:"
}
}
Redis Sentinel for High Availability
{
"Redis": {
"Configuration": "sentinel1:26379,sentinel2:26379,sentinel3:26379,serviceName=mymaster",
"InstanceName": "ShirinzadShop:"
}
}