Skip to main content

id: performance

Performance & Caching in SophiChain

ABP Framework Caching Features


id: performance

📖 Overview

SophiChain uses ABP's IDistributedCache for caching to improve performance and reduce database load.


id: performance

🏗️ Distributed Caching

1. Inject IDistributedCache

public class ExchangeRateService : ITransientDependency
{
private readonly IDistributedCache _cache;
private readonly IExchangeRateRepository _repository;

private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(5);
}

id: performance

2. Cache Usage Patterns

Set Cache:

var cacheKey = $"Rate:{from}:{to}";
await _cache.SetStringAsync(
cacheKey,
JsonSerializer.Serialize(data),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = _cacheDuration
});

Get from Cache:

var cached = await _cache.GetStringAsync(cacheKey);
if (cached != null)
return JsonSerializer.Deserialize<RateDto>(cached);

Cache-Aside Pattern:

public async Task<decimal> GetRateAsync(string from, string to)
{
var key = $"Rate:{from}:{to}";
var cached = await _cache.GetStringAsync(key);
if (cached != null)
return JsonSerializer.Deserialize<decimal>(cached);

var rate = await _repository.GetRateAsync(from, to);
await _cache.SetStringAsync(key, JsonSerializer.Serialize(rate));
return rate;
}

Invalidate Cache:

await _cache.RemoveAsync($"Rate:{from}:{to}");

id: performance

⚙️ Cache Configuration

Redis (Production)

public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
options.InstanceName = "SophiChain:";
});
}

In-Memory (Development)

context.Services.AddDistributedMemoryCache();

id: performance

🎯 Caching Strategies

Cache-Aside (Most Common)

public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory)
{
var cached = await _cache.GetStringAsync(key);
if (cached != null)
return JsonSerializer.Deserialize<T>(cached);

var value = await factory();
await _cache.SetStringAsync(key, JsonSerializer.Serialize(value));
return value;
}

Write-Through

await _repository.InsertAsync(rate);
await _cache.SetStringAsync(key, JsonSerializer.Serialize(rate));

id: performance

✅ Best Practices

When to Cache

  • ✅ Frequently accessed data
  • ✅ Rarely changing data (e.g., exchange rates, currencies)
  • ✅ Expensive queries
  • ✅ External API responses
  • ❌ User-specific data (unless short TTL)
  • ❌ Rapidly changing data
  • ❌ Large objects

Cache Keys

  • ✅ Use descriptive prefixes: ExchangeRate:USD:EUR
  • ✅ Include tenant ID for multi-tenant: Tenant:{id}:Rate:{from}:{to}
  • ✅ Use consistent naming convention
  • ❌ Don't use user-generated content in keys
  • ❌ Don't use special characters

Cache Expiration

  • ✅ Set appropriate TTL based on data volatility
  • ✅ Use shorter TTL for frequently changing data
  • ✅ Use longer TTL for static data
  • ✅ Implement cache warming for critical data
  • ❌ Don't cache forever
  • ❌ Don't use same TTL for all data

Cache Invalidation

  • ✅ Invalidate on data updates
  • ✅ Use cache tags for group invalidation
  • ✅ Log cache operations
  • ❌ Don't forget to invalidate after updates
  • ❌ Don't invalidate too frequently

id: performance

📊 Query Optimization

Filter at database level:

// ❌ Bad
var all = await _repository.GetListAsync();
var active = all.Where(w => w.IsActive).ToList();

// ✅ Good
var query = await _repository.GetQueryableAsync();
var active = await AsyncExecuter.ToListAsync(query.Where(w => w.IsActive));

Best Practices:

  • ✅ Use indexes on frequently queried columns
  • ✅ Use WithDetails() for eager loading
  • ✅ Apply pagination for large datasets
  • ❌ Don't load entire collections unnecessarily

id: performance

📖 References


id: performance