Azure Cache for Redis — Tiers Deep Dive
Companion to the basics in Azure Cache for Redis. This topic goes deeper on tier selection, clustering, persistence, eviction, and network isolation — the levers a senior engineer is expected to choose deliberately, not by accident.
Key Points
- Five tiers: Basic, Standard, Premium, Enterprise, Enterprise Flash. Pick on SLA, clustering, persistence, modules, and network isolation — not just memory size.
- Standard is the smallest viable production tier (HA pair, 99.9% SLA). Basic has no SLA — dev only.
- Premium unlocks clustering, RDB+AOF persistence, VNet injection, geo-replication (passive), Redis-on-Flash via NVMe.
- Enterprise / Enterprise Flash are the Redis Inc. binary — adds modules (RediSearch, RedisJSON, RedisBloom, RedisTimeSeries) and active-active geo-replication (CRDT-based).
- Eviction policy in prod: choose deliberately.
noevictionfails writes when full (safe for "don't lose data");allkeys-lrusilently drops cold keys (safe for cache). - Avoid blocking commands:
KEYS *, big-keyMGET, unboundedSCAN. They stall the entire single-threaded server. - Network isolation: VNet injection (Premium, legacy) vs Private Link (newer, recommended for all tiers that support it).
- TLS is enforced: non-TLS port 6379 is disabled by default; clients must use 6380 + TLS.
Concepts (deep dive)
The base Azure Cache for Redis topic frames what Redis is and how to use it from .NET. This file is about the operations decisions — which of the five tiers you actually need, how clustering changes the failure model, what persistence buys you (and what it doesn't), and which eviction policy fits your cache vs your durable-ish data. The throughline: every choice below is a trade-off, and "Premium" is not always the right answer — but Basic almost never is in production.
Tier comparison — what actually changes
| Capability | Basic | Standard | Premium | Enterprise | Enterprise Flash |
|---|---|---|---|---|---|
| Topology | Single node | Primary+replica | Primary+replica + cluster | Active-active cluster | Active-active + NVMe |
| SLA | ❌ none | ✅ 99.9% | ✅ 99.9% (99.95% w/ zones) | ✅ 99.99% | ✅ 99.99% |
| Clustering | ❌ | ❌ | ✅ up to 10 shards | ✅ | ✅ |
| Persistence (RDB/AOF) | ❌ | ❌ | ✅ | ✅ | ✅ |
| VNet injection | ❌ | ❌ | ✅ (legacy) | ❌ | ❌ |
| Private Link | ✅ | ✅ | ✅ | ✅ | ✅ |
| Geo-replication | ❌ | ❌ | ✅ passive | ✅ active-active | ✅ active-active |
| Modules (Search/JSON/Bloom/TS) | ❌ | ❌ | ❌ | ✅ | ✅ |
| Storage tier | RAM | RAM | RAM | RAM | RAM + NVMe |
| Cost (relative) | $ | $$ | $$$ | $$$$ | $$$ (big caches) |
Mental model:
Basic → "It's just Redis on one VM, no SLA" — dev/test only
Standard → "HA pair" — smallest prod
Premium → "Cluster + persistence + VNet" — serious prod
Enterprise → "Redis Inc. binary, modules, A-A geo"— search/JSON/multi-region
Ent. Flash → "Big cache cheap via NVMe" — TBs of cold-ish data
Clustering (Premium+)
Hash slots: keys are hashed to one of 16,384 slots. Cluster shards own ranges of slots. A single key always lives on one shard.
Cluster: 3 shards
┌────────────────┬────────────────┬────────────────┐
│ Shard 0 │ Shard 1 │ Shard 2 │
│ slots 0..5460 │ slots 5461.. │ slots 10923.. │
└────────────────┴────────────────┴────────────────┘
Multi-key ops require keys on the same shard. Use hashtags to force colocation:
// Both keys hash by "user:42" → same shard
await db.StringSetAsync("{user:42}:profile", profileJson);
await db.StringSetAsync("{user:42}:settings", settingsJson);
// MGET works because both are on one shard
var values = await db.StringGetAsync(new RedisKey[] { "{user:42}:profile", "{user:42}:settings" });
Client must be cluster-aware. StackExchange.Redis handles this automatically when you connect with all endpoints:
ConnectionMultiplexer.Connect("mycache.redis.cache.windows.net:6380,password=...,ssl=True,abortConnect=false");
The client discovers cluster topology via CLUSTER NODES and routes commands to the right shard. MOVED/ASK redirects are followed transparently.
💡 Shard sizing: more shards = more parallelism but more cross-shard pain. Start with 3-6; scale up only if a single shard's CPU is saturated.
Persistence (Premium+)
Two mechanisms, both write to Azure Storage:
| Mechanism | What it does | RPO | Overhead |
|---|---|---|---|
| RDB | Periodic point-in-time snapshot | 15min – 24h | Low (fork+copy) |
| AOF | Append every write to log | seconds | High (fsync per write) |
RDB: ────●────────●────────●──────── (snapshots every N min)
AOF: ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● (every write logged)
RDB pros: small files, fast restore, low overhead. AOF pros: tiny RPO; replay log to recover near-current state. AOF cons: write amplification — every SET hits disk.
Default for Premium: RDB at 60-minute intervals. Enable AOF only when data loss budget is single-digit seconds and you accept ~30% throughput loss.
⚠️ Persistence is not a backup. It survives node restart, not "I deleted the key by mistake". For DR, export RDB snapshots out of band.
Geo-replication
Premium — passive replica:
Failover is manual via Azure API. Replica is read-only until promoted. RPO ~ seconds; RTO ~ minutes.
Enterprise — active-active (CRDT):
Region A ◀──── bidirectional replication ────▶ Region B
▲ ▲
│ │
▼ ▼
Clients (read+write) Clients (read+write)
Both regions accept writes. Conflicts resolved via CRDTs (Conflict-free Replicated Data Types). Strings, counters, sets, hashes all have CRDT semantics.
✅ Use Enterprise A-A when: multi-region writes, sub-100ms regional latency, can tolerate eventual consistency. ❌ Avoid for: strict ordering, transactions across regions.
Eviction policies — choose deliberately
When maxmemory is hit, Redis evicts based on maxmemory-policy:
| Policy | Behavior | Use when |
|---|---|---|
noeviction | New writes fail with OOM error | "Don't silently lose data" — durable cache, queues |
allkeys-lru | Evict least-recently-used across all keys | General cache (works even for keys without TTL) |
allkeys-lfu | Evict least-frequently-used | Hot/cold pattern; protect frequent keys |
volatile-lru | LRU among keys with TTL | Mixed: persistent set + cached set |
volatile-lfu | LFU among TTL'd keys | Same, hot/cold |
volatile-ttl | Evict shortest-TTL first | Streaming windows |
allkeys-random | Random eviction | Almost never |
⚠️ The Azure default is volatile-lru — but if your code doesn't set TTLs, eviction does nothing and writes start failing. Either always set TTL or switch to allkeys-lru.
Memory tuning
INFO memory
used_memory_human: 8.2G
maxmemory_human: 13G
mem_fragmentation_ratio: 1.4 ← > 1.5 = consider defrag or restart
Knobs you actually tune:
maxmemory— set in tier; can't exceed SKU.maxmemory-policy— eviction policy above.maxmemory-reserved— Azure reserves a chunk for non-cache (replication buffer, OS).maxfragmentationmemory-reserved— reserved for fragmentation; bump up if you see OOM under fragmentation.
Patterns to avoid (single-threaded server hazards)
Redis processes commands on one thread. Any slow command stalls all clients.
// ❌ KEYS scans entire keyspace — O(N), blocks server
var matches = db.Execute("KEYS", "user:*");
// ✅ SCAN is incremental
foreach (var k in server.Keys(pattern: "user:*", pageSize: 100)) { ... }
// ❌ MGET 10,000 keys at once — big payload, big latency spike
var values = await db.StringGetAsync(thousandsOfKeys);
// ✅ Chunk
foreach (var batch in keys.Chunk(200))
await db.StringGetAsync(batch);
// ❌ Big-key writes (50MB JSON blob)
await db.StringSetAsync("session:42", giantJson);
// ✅ Split, or move to blob storage with Redis pointer
await db.StringSetAsync("session:42", "blob://sessions/42.json");
⚠️ Big keys (>100KB) and hot keys (one shard handles everything) are the top causes of latency outliers in production.
Keyspace notifications (cache invalidation)
Subscribe to events:
var sub = mux.GetSubscriber();
await sub.SubscribeAsync(RedisChannel.Pattern("__keyevent@0__:expired"),
(ch, key) => InvalidateLocalCache(key.ToString()));
✅ Use for L1/L2 cache invalidation — when Redis evicts/expires, local memory caches drop the same key. ❌ Do NOT use for durable event delivery — notifications are pub/sub (best-effort, no persistence).
Network isolation
| Option | Tier | Notes |
|---|---|---|
| Public endpoint + firewall IPs | All | Allowlist source IPs |
| Private Link | All (recommended) | Private endpoint in your VNet; cache stays public-resolvable but routes privately |
| VNet injection | Premium only | Cache deployed into your VNet subnet (legacy; complex) |
Microsoft now recommends Private Link over VNet injection for new deployments. VNet injection has subnet sizing constraints and migration headaches.
TLS enforcement
- Port 6380 (TLS) is the only port enabled by default.
- Minimum TLS version is 1.2 (1.3 supported on newer SKUs).
- Older clients (Redis CLI < 6, libraries that don't speak TLS): need a stunnel proxy.
var options = ConfigurationOptions.Parse("mycache.redis.cache.windows.net:6380");
options.Ssl = true;
options.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
How it works under the hood
Azure Cache for Redis (Premium, clustered)
┌────────────────────────────────────────────────────────┐
│ │
┌────┤ Shard 0 Primary ─── replication ──▶ Replica │
│ │ Shard 1 Primary ─── replication ──▶ Replica │
│ │ Shard 2 Primary ─── replication ──▶ Replica │
│ │ │
│ │ ┌── RDB snapshot ──▶ Azure Storage (persistence) │
│ │ └── AOF append ──▶ Azure Storage │
│ │ │
│ │ Private endpoint ◀─────── customer VNet │
│ └────────────────────────────────────────────────────────┘
│
└─ async geo-replicate ──▶ secondary region (passive replica)
Failover (Standard/Premium): when primary fails, replica is promoted. Connection multiplexer reconnects (transparent). Window: ~30-60s of read-only or refused writes.
Cluster rebalance: adding a shard moves slot ranges. Redis migrates keys live; clients follow MOVED/ASK redirects. No downtime, but expect transient elevated latency.
Code: correct vs wrong
❌ Wrong: Basic tier in production
✅ Correct: Standard minimum for prod
❌ Wrong: cluster client without hashtags for multi-key ops
// On a clustered cache, this throws "CrossSlot" if keys hash to different shards
await db.StringGetAsync(new RedisKey[] { "user:42:profile", "user:42:settings" });
✅ Correct: hashtag colocation
❌ Wrong: relying on eviction with no TTLs and volatile-lru
await db.StringSetAsync(key, value); // no TTL
// Policy = volatile-lru → eviction never triggers → writes fail at maxmemory
✅ Correct: always set TTL OR use allkeys-lru
❌ Wrong: KEYS in production
✅ Correct: SCAN
var server = mux.GetServer(endpoint);
foreach (var key in server.Keys(pattern: "user:*", pageSize: 100)) { ... }
Design patterns for this topic
Pattern 1 — "Premium with persistence for session state"
- Intent: sessions survive node restart.
- How: Premium + AOF (or RDB if seconds-RPO ok).
Pattern 2 — "Enterprise for search/JSON workloads"
- Intent: offload secondary-index queries from primary DB.
- How: Enterprise tier + RediSearch + RedisJSON modules.
Pattern 3 — "Hashtag colocation for multi-key transactions"
- Intent: atomic ops on related keys in cluster.
- How:
{tenant:X}:keyA,{tenant:X}:keyB.
Pattern 4 — "Private Link only"
- Intent: no public exposure; defense in depth.
- How: disable public endpoint; private endpoint in app VNet.
Pattern 5 — "Active-active for multi-region writes"
- Intent: low-latency writes from any region; CRDT-resolved.
- How: Enterprise tier + active-geo-replication group.
Pros & cons / trade-offs
| Tier | Pros | Cons |
|---|---|---|
| Basic | Cheapest | No SLA — dev only |
| Standard | HA, simple | No cluster, no persistence, no VNet |
| Premium | Cluster, persistence, geo (passive) | $$$, VNet legacy |
| Enterprise | Modules, A-A geo, 99.99% | $$$$, Redis Inc. licensing in price |
| Enterprise Flash | Big caches cheap | NVMe latency > RAM; not for hot paths |
When to use / when to avoid
- Use Standard as the default for prod. Upgrade only when you need a Premium feature.
- Use Premium for clustering (>26 GB), persistence, VNet, single-region geo passive.
- Use Enterprise for RediSearch/RedisJSON or active-active multi-region.
- Use Enterprise Flash when you need TBs of cache and most data is cold-ish.
- Avoid Basic in production. No SLA = inevitable outage.
- Avoid VNet injection for new caches — use Private Link.
- Avoid clustering if you don't actually need it. Single-shard ops are simpler.
Interview Q&A
Q1. What's the smallest production-viable tier? Standard — primary+replica with 99.9% SLA. Basic has no SLA.
Q2. When do you need Premium? Clustering (>26 GB or sharded throughput), persistence (RDB/AOF), VNet integration, or passive geo-replica.
Q3. RDB vs AOF? RDB = periodic snapshot, low overhead, minutes RPO. AOF = log every write, seconds RPO, high overhead. Choose based on data-loss budget.
Q4. Why can't I MGET across keys in a cluster? Multi-key ops require keys on the same shard. Use {hashtag} to colocate.
Q5. What's noeviction and when use it? Writes fail when memory full instead of evicting. Use when "lose data silently" is unacceptable — durable counters, queues.
Q6. Premium passive vs Enterprise active-active geo? Passive: replica read-only, manual failover. Active-active: both regions writeable, CRDT conflict resolution, sub-100ms regional latency.
Q7. What modules does Enterprise add? RediSearch (full-text + secondary index), RedisJSON (native JSON), RedisBloom (probabilistic structures), RedisTimeSeries.
Q8. VNet injection vs Private Link? VNet injection is Premium-only and legacy. Private Link works on all tiers and is the current recommendation.
Q9. Why is KEYS * dangerous? Single-threaded server: O(N) scan blocks all other clients. Use SCAN.
Q10. Hot key — how do you find and fix? Find: redis-cli --hotkeys or MONITOR briefly. Fix: shard the key (e.g., counter:{0..9}), use client-side aggregation, or move to a different store.
Q11. TLS enforcement? Port 6380 only by default; minimum TLS 1.2. Plain port 6379 disabled.
Q12. What's Enterprise Flash for? Big caches (TBs) with NVMe-backed values and RAM-backed keys/indexes. Cheaper $/GB than Enterprise; latency higher than RAM-only.
Gotchas / common mistakes
- ⚠️ Basic in production — you will hit unplanned downtime.
- ⚠️
volatile-lruwith no TTLs — eviction never fires; writes fail. - ⚠️ Big keys (>100KB) — latency spikes; replication lag.
- ⚠️ KEYS / FLUSHALL on prod — server stall.
- ⚠️ Clustering without hashtags — CrossSlot errors on multi-key.
- ⚠️ Persistence ≠ backup — accidental DEL is replicated to disk too.
- ⚠️ VNet injection lock-in — migrating to Private Link requires recreate.
- ⚠️ Active-active assumed strongly consistent — it's CRDT-eventual.