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:

  1. Cache invalidation is called after updates
  2. TTL is not too long
  3. Redis server is running
  4. Connection string is correct

Issue: Slow Performance

Check:

  1. Redis server resources (CPU, memory)
  2. Network latency between app and Redis
  3. Serialization overhead (use efficient serializer)
  4. 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:"
  }
}