Appearance
Azure Managed Redis
Each Microtec ERP environment has a dedicated Azure Managed Redis instance. Redis is used for distributed caching (reducing database load on frequently read data) and as the storage backend for ASP.NET Core DataProtection key ring sharing across container replicas.
Azure Managed Redis — Not Azure Cache for Redis
Microtec ERP uses Azure Managed Redis (the newer in-memory data grid product), which is a completely different product from the legacy Azure Cache for Redis. The SKU family (Balanced_B0, Balanced_B1, etc.) and some endpoint patterns differ from the old Basic C0/Standard C1/Premium P1 SKUs.
Instance Inventory
| Environment | Redis Hostname | Port | SSL | Resource Group |
|---|---|---|---|---|
| dev | mic-erp-be-dev-redis.{region}.redis.azure.net | 10000 | Yes | mic-erp-be-dev-utils-rg |
| stage | mic-erp-be-stage-redis.uksouth.redis.azure.net | 10000 | Yes | mic-erp-be-stage-utils-rg |
| preprod | mic-erp-be-preprod-redis.{region}.redis.azure.net | 10000 | Yes | mic-erp-be-preprod-utils-rg |
| uat | mic-erp-be-uat-redis.{region}.redis.azure.net | 10000 | Yes | mic-erp-be-uat-utils-rg |
| production | mic-erp-be-production-redis.{region}.redis.azure.net | 10000 | Yes | mic-erp-be-production-utils-rg |
Stage Redis Hostname
The stage Redis instance uses the .uksouth.redis.azure.net suffix because it was provisioned in UK South. All Azure Managed Redis instances use port 10000 (not 6380 which is the legacy Azure Cache for Redis SSL port). Do not change the connection string format for stage.
Connection String Format
All backend services use the StackExchange.Redis connection string format:
{hostname}:{port},password={password},ssl=True,abortConnect=False,connectTimeout=10000,syncTimeout=5000Per-Environment Examples
# dev
mic-erp-be-dev-redis.{region}.redis.azure.net:10000,password={from-kv},ssl=True,abortConnect=False
# stage
mic-erp-be-stage-redis.uksouth.redis.azure.net:10000,password={from-kv},ssl=True,abortConnect=False
# production
mic-erp-be-production-redis.{region}.redis.azure.net:10000,password={from-kv},ssl=True,abortConnect=FalseThe password for each environment is stored in Key Vault:
| Environment | Key Vault | Secret Name |
|---|---|---|
| dev | mic-erp-be-dev-skv | RedisConfiguration--Password |
| stage | mic-erp-stg-kv | RedisConfiguration--Password |
| preprod | mic-erp-be-preprod-skv | RedisConfiguration--Password |
| uat | mic-erp-uat-kv | RedisConfiguration--Password |
The full connection string (including host, port, and password) is assembled in appsettings.json at runtime:
json
{
"Redis": {
"Configuration": "mic-erp-be-dev-redis.{region}.redis.azure.net:10000,password={Redis__Password},ssl=True,abortConnect=False"
}
}SKU and Capacity
Azure Managed Redis uses a different SKU family from the legacy Azure Cache for Redis:
| Environment | SKU | Notes |
|---|---|---|
| dev | Balanced_B0 | Smallest non-prod tier |
| stage | Balanced_B1 | Default tier |
| preprod | Balanced_B1 | Default tier |
| uat | Balanced_B1 | Default tier |
| production | Balanced_B1 | Default tier (scale up as needed) |
Balanced Tier
The Balanced_B tier is part of the Azure Managed Redis in-memory data grid SKU family. It is completely different from the legacy Basic C/Standard C/Premium P tiers of Azure Cache for Redis. Do not mix up the two products when reading Azure documentation or Bicep reference templates.
Usage in Backend Services
1. Distributed Caching
Backend services cache frequently-read, rarely-changed data to avoid repeated database queries:
csharp
// Shared.Infrastructure/Caching/RedisCacheService.cs
public async Task<T?> GetOrSetAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? expiry = null)
{
var cached = await _db.StringGetAsync(key);
if (cached.HasValue)
return JsonSerializer.Deserialize<T>(cached!);
var value = await factory();
await _db.StringSetAsync(
key,
JsonSerializer.Serialize(value),
expiry ?? TimeSpan.FromMinutes(5)
);
return value;
}Typical cache keys and TTLs:
| Cache Key Pattern | TTL | Description |
|---|---|---|
tenant:{id}:branches | 10 min | Branch list for tenant |
tenant:{id}:currencies | 1 hour | Currency settings |
accounts:{tenantId}:coa | 5 min | Chart of accounts |
user:{id}:permissions | 5 min | ERP permission codes (mirrors Keycloak mapper cache) |
dropdown:{entity}:{tenantId} | 5 min | Dropdown list data |
2. ASP.NET Core DataProtection
All backend services share a single key ring stored in Redis to ensure encrypted cookies and tokens issued by one replica can be decrypted by any other:
csharp
// Program.cs — DataProtection setup
builder.Services.AddDataProtection()
.SetApplicationName("microtec-erp")
.PersistKeysToStackExchangeRedis(
ConnectionMultiplexer.Connect(redisConfig),
"DataProtection-Keys"
)
.ProtectKeysWithAzureKeyVault(
new Uri($"https://{kvName}.vault.azure.net/keys/DataProtection"),
new DefaultAzureCredential()
);DataProtection Key Sharing is Critical
If Redis is unavailable at startup, DataProtection key loading fails and the service cannot start. Monitor Redis connectivity as a top-priority health signal. All services across all replicas MUST use the same Redis instance for DataProtection to function correctly.
3. Distributed Lock (Redlock)
The Workflow service uses Redis for distributed locks to prevent concurrent execution of the same workflow step across replicas:
csharp
// WorkflowLockService.cs
public async Task<IDisposable?> AcquireLockAsync(string resourceKey, TimeSpan expiry)
{
var lockKey = $"lock:workflow:{resourceKey}";
var token = Guid.NewGuid().ToString();
bool acquired = await _db.StringSetAsync(
lockKey, token, expiry,
When.NotExists
);
return acquired ? new RedisMutex(_db, lockKey, token) : null;
}Bicep Configuration
bicep
// redis.bicep
resource redis 'Microsoft.Cache/redisEnterprise@2024-02-01' = {
name: '${resourcePrefix}-redis'
location: location
sku: {
name: redisSku // e.g. 'Balanced_B0', 'Balanced_B1'
capacity: 1
}
properties: {
minimumTlsVersion: '1.2'
}
}
resource redisDatabase 'Microsoft.Cache/redisEnterprise/databases@2024-02-01' = {
name: 'default'
parent: redis
properties: {
evictionPolicy: 'AllKeysLRU'
clusteringPolicy: 'NonClustered'
port: 10000
}
}Monitoring
Key metrics to monitor via Azure Monitor:
| Metric | Warning Threshold | Critical Threshold |
|---|---|---|
UsedMemoryPercentage | > 70% | > 85% |
CacheHits (rate) | < 80% | < 60% |
CacheMisses (rate) | — | — |
ConnectedClients | > 80 | > 120 |
Evictions | > 10/min | > 100/min |
High eviction rates indicate the Redis instance needs a larger SKU.
Flushing Cache (Development)
Caution in Shared Environments
Flushing Redis in stage/preprod clears DataProtection keys, which invalidates all active user sessions. Only flush dev Redis without coordination.
bash
# Connect to Redis CLI (requires redis-cli installed)
redis-cli -h mic-erp-be-dev-redis.{region}.redis.azure.net \
-p 10000 \
--tls \
-a '{password}' \
FLUSHDB
# Or flush only cache keys (preserve DataProtection keys)
redis-cli ... --scan --pattern 'tenant:*' | xargs redis-cli ... DEL
redis-cli ... --scan --pattern 'dropdown:*' | xargs redis-cli ... DELTroubleshooting
| Symptom | Likely Cause | Resolution |
|---|---|---|
| Service fails to start: "DataProtection key load failed" | Redis unreachable | Check NSG rules; verify Redis connection string in Key Vault |
| All users logged out suddenly | DataProtection keys evicted from Redis | Redis memory full (maxmemory-policy: allkeys-lru evicted DP keys); upgrade SKU |
| High cache miss rate | TTL too short or key pattern mismatch | Review cache key patterns; check for cache key prefix mismatches across services |
| Connection timeout | Wrong port — Azure Managed Redis uses port 10000 (not 6380) | Verify connection string uses port 10000 |
Related Documentation
- Key Vault — Redis password secret storage
- Container Apps — Service Bus scaling trigger (Notification)
- Networking —
datasubnet where Redis private endpoint lives - Bicep Modules —
redis.bicepmodule details