Redis
In Part 1, we explored architectural abstraction and failure-rate-based circuit isolation using Polly.
But circuit breaking alone does not make a system resilient.
Opening the circuit simply stops calling Redis.
The real question is:
What happens next?
In high-throughput healthcare platforms — where ontology lookups, terminology resolution, and patient context queries are frequent and latency-sensitive — a cache failure must not translate into a service outage.
That requires:
This article dives into those mechanics.
The goal is simple:
If Redis fails or misses, the system should:
The calling microservice should not need to know whether:
That abstraction lives inside RedisCacheClient.
public async Task<T> GetData<T>(
string key,
Func<Task<T>> dataFunction,
int expireTimeInHour = 24,
bool isReset = false,
bool readReplica = false)
{
if (!isReset)
{
var data = await GetDataAsync<T>(key, readReplica);
if (!CheckNullOrEmpty(data))
{
logger.LogInformation("Cache HIT for key: {Key}", key);
return data;
}
}
logger.LogInformation("Cache MISS for key: {Key} -- querying Cosmos DB", key);
var result = await dataFunction();
await Task.Run(() => SetData(key, result, expireTimeInHour));
return result;
}
Key behaviors:
This is a clean read-through pattern.
There are scenarios where underlying data changes.
Instead of manually clearing keys across services, the API allows:
NamedIndividual_TQ namedIndividualTQ = await redisCacheClient.GetData(
key: cacheKey,
dataFunction: () => cosmosRepository.GetNamedIndividualAsync(id),
isReset: true);
When isReset = true:
This ensures:
In healthcare systems, data correctness can outweigh performance.
This flag provides explicit control.
Azure Cache for Redis supports read replicas.
For read-heavy workloads (e.g., ontology lookups), routing to replicas prevents primary saturation.
Implementation:
private T ExecuteGetData<T>(string key, bool readReplica = false)
{
var db = GetDataBase();
var flag = readReplica ? CommandFlags.PreferReplica : CommandFlags.None;
var value = db.StringGet(key, flag);
return value.HasValue
? helper.Deserialize<T>(value.ToString())
: default(T);
}
With:
await redisCacheClient.GetDataAsync<NamedIndividual_TQ>(key, useReplica: true);
This allows horizontal read scaling without changing business logic.
Architecturally, this prevents a read-heavy service from overwhelming the primary Redis node.
Let’s simulate.
System behavior:
This is controlled degradation.
However, this introduces a new risk:
Fallback amplification.
If Redis fails during high traffic:
Additionally:
If a popular key expires under high concurrency:
This is the thundering herd problem.
While this design protects Redis isolation, it assumes:
Cosmos DB can absorb fallback load.
In moderate SaaS scale, this is acceptable.
At hyperscale, you would add:
Understanding this boundary is part of architectural maturity.
Resilience without observability is incomplete.
Every Redis operation is tracked as a dependency:
private T Execute<T>(Func<T> redisOperation, string operationName)
{
var operation = telemetry.StartOperation<DependencyTelemetry>(operationName);
operation.Telemetry.Type = "Azure Redis";
operation.Telemetry.Target = connectionString;
try
{
var result = redisOperation();
operation.Telemetry.Success = true;
return result;
}
catch (Exception ex)
{
operation.Telemetry.Success = false;
operation.Telemetry.Properties["Error"] = ex.Message;
throw;
}
finally
{
telemetry.StopOperation(operation);
}
}
Additionally:
This enables:
In distributed healthcare platforms, auditability and visibility are operational requirements.
All resilience parameters are externalized via appsettings.json.
Example:
{
"Redis": {
"UseCircuitBreaker": true,
"CircuitBreaker": {
"TimeoutMs": 3000,
"FailureThreshold": 0.5,
"SamplingDurationSeconds": 30,
"MinimumThroughput": 100,
"DurationOfBreakSeconds": 30
}
}
}
This allows:
In production systems, configuration agility reduces MTTR.
Version 2.10 removed nullable TimeSpan? support for expiry in StringSet.
Old approach (now invalid):
db.StringSet(key, value, expiry: TimeSpan.FromHours(24));
Updated implementation:
db.StringSet(key, serialisedValue);
if (expiry.HasValue)
db.KeyExpire(key, expiry.Value);
Async variant:
await db.StringSetAsync(key, serialisedValue);
if (expiry.HasValue)
await db.KeyExpireAsync(key, expiry.Value);
This preserves backward compatibility and avoids runtime errors after package upgrades.
Operational detail matters.
Beyond key-value strings, list operations are supported:
All follow the same:
Consistency across API surface ensures no hidden unprotected paths.
Under normal load:
During Redis degradation:
Under extreme traffic spike + Redis outage:
The system favors:
Predictable degradation over unpredictable collapse.
In healthcare platforms:
This design ensures:
Redis instability = performance event Not = availability event
That distinction protects operational continuity.
This implementation demonstrates:
Circuit isolation prevents failure from spreading.
Fallback ensures continuity.
Telemetry ensures visibility.
Together, they form a resilient, production-grade caching strategy suitable for high-throughput, healthcare-grade distributed systems.
Share this:

Part 4 of our series on intent-driven development. Start with Part 1, or read Parts 2 and 3 first if you want the technical workflow before the outcomes. The first three posts in this series covered the mechanics: why the spec is now the source of truth, how to build the CLAUDE.md context layer, and how OpenSpec moves […]

Part 3 of our series on intent-driven development. Read Part 1 (spec-driven development with Kiro) and Part 2 (mastering the CLAUDE.md file) first. Part 1 of this series established the principle: in AI-assisted development, the spec is the source of truth, not the code. Part 2 covered the CLAUDE.md file — the context layer that ensures every AI session starts from […]

How to Master the CLAUDE.md File: The Context Layer That Makes Spec-Driven Development Work Part 2 of our series on intent-driven development. If you haven't read Part 1 — Code is No Longer the Source of Truth. Your Spec Is. — start there. In Part 1, we explored how spec-driven development with tools like Kiro shifts the source of truth from […]
Partner with CloudIQ to achieve immediate gains while building a strong foundation for long-term, transformative success.