Hellnet.Cache
1.1.15
dotnet add package Hellnet.Cache --version 1.1.15
NuGet\Install-Package Hellnet.Cache -Version 1.1.15
<PackageReference Include="Hellnet.Cache" Version="1.1.15" />
<PackageVersion Include="Hellnet.Cache" Version="1.1.15" />
<PackageReference Include="Hellnet.Cache" />
paket add Hellnet.Cache --version 1.1.15
#r "nuget: Hellnet.Cache, 1.1.15"
#:package Hellnet.Cache@1.1.15
#addin nuget:?package=Hellnet.Cache&version=1.1.15
#tool nuget:?package=Hellnet.Cache&version=1.1.15
Hellnet.Cache
Biblioteca de cache multi-camada para .NET com L1 (memória in-process), L2 (Valkey/Redis) e L3 opcional (cache externo).
Write-through, read-through, proteção contra cache stampede, configuração env-first, resiliência com Polly.
Instalação
dotnet add package Hellnet.Cache
Quick start
Env-first (recomendado para microserviços)
builder.Services.AddHellnetCache();
Exige as env vars HELLNET_CACHE_VALKEY_CONNECTION e HELLNET_CACHE_VALKEY_PASSWORD. Lança InvalidOperationException no startup se faltar.
Explicito
builder.Services.AddHellnetCache(new HellnetCacheOptions
{
ValkeyConnection = "valkey.hellnet.com.br:6379",
ValkeyPassword = "hellnet2026",
});
Configuração mínima
export HELLNET_CACHE_VALKEY_CONNECTION=valkey.hellnet.com.br:6379
export HELLNET_CACHE_VALKEY_PASSWORD=hellnet2026
Uso
public class OrderService(ICache cache)
{
// Set com TTL por chave
public async Task SetOrderAsync(Order order)
=> await cache.SetAsync($"order:{order.Id}", order, TimeSpan.FromHours(1));
public async Task SetSessionAsync(string id, Session s)
=> await cache.SetAsync($"session:{id}", s, TimeSpan.FromMinutes(5));
// Get
public async Task<Order?> GetOrderAsync(string id)
=> await cache.GetAsync<Order>($"order:{id}");
// GetOrSet — protegido contra stampede
public async Task<Config> GetConfigAsync()
=> await cache.GetOrSetAsync("config:global",
() => _db.Configs.FirstAsync(),
TimeSpan.FromHours(24));
// Remove
public async Task InvalidateOrderAsync(string id)
=> await cache.RemoveAsync($"order:{id}");
// Exists
public async Task<bool> HasOrderAsync(string id)
=> await cache.ExistsAsync($"order:{id}");
}
Camadas
| Layer | Provider | TTL default | Comportamento em falha |
|---|---|---|---|
| L1 | MemoryCacheProvider (in-process) |
5 min | Sempre saudável |
| L2 | ValkeyCacheProvider (StackExchange.Redis + Polly) |
30 min | Degradação graciosa — retorna null, loga warning |
| L3 | ExternalCacheProvider (pluggable) |
15 min | Desabilitado por padrão |
Read-through
Get("key") → L1 → L2 → L3 → miss/null
Quando um hit ocorre em L2 ou L3, as camadas inferiores são populadas automaticamente (warming assíncrono e deduplicado).
Write-through
Set("key") → L1.Set + L2.Set + L3.Set em paralelo (Task.WhenAll)
TTL por chave
Cada SetAsync/GetOrSetAsync aceita TimeSpan? ttl. Quando omitido, usa fallback por camada:
await cache.SetAsync("a", v, TimeSpan.FromMinutes(5)); // 5 min
await cache.SetAsync("b", v, TimeSpan.FromHours(24)); // 24h
await cache.SetAsync("c", v); // fallback: DefaultTtl (30min)
| Chamada | L1 (Memory) | L2 (Valkey) | L3 (External) |
|---|---|---|---|
Set(k, v) |
L1DefaultTtl (5min) |
DefaultTtl (30min) |
ExternalDefaultTtl (15min) |
Set(k, v, 1h) |
1h | 1h | 1h |
L1 usa expiração absoluta por padrão (L1SlidingExpiration=false). Sliding disponível como opt-in.
Concorrência
| Mechanism | O que previne |
|---|---|
| Per-key SemaphoreSlim | Cache stampede em GetOrSetAsync — só 1 request executa a factory por chave |
| Auto-cleanup | TryRemove + Dispose após factory — sem memory leak |
| Warming dedup | ConcurrentDictionary flag — só 1 warming por chave por vez |
| Touch-on-read | TouchOnRead=true estende TTL em todas camadas no hit |
| Parallel writes | Task.WhenAll — Set/Remove em todas camadas simultaneamente |
Resiliência (Polly)
| Mecanismo | Comportamento |
|---|---|
| Retry | Exponential backoff com jitter (200ms → 400ms → 800ms) |
| Circuit breaker | 5 falhas consecutivas → abre por 30s → half-open |
| Degradação | Toda falha retorna null/false, nunca throw |
Valkey timeout → Retry 200ms → Retry 400ms → CB abre (30s) → fail fast → CB half-open → ok? fecha
Options
Env vars (HELLNET_CACHE_*)
| Env var | Default | Descrição |
|---|---|---|
VALKEY_CONNECTION |
(obrigatório) | Valkey host:port |
VALKEY_PASSWORD |
— | Valkey password |
VALKEY_KEY_PREFIX |
hellnet:cache: |
Prefixo de chaves no Valkey |
L1_DEFAULT_TTL |
00:05:00 |
L1 fallback TTL |
DEFAULT_TTL |
00:30:00 |
Global fallback TTL |
MAX_TTL |
24:00:00 |
Safety cap |
TOUCH_ON_READ |
false |
Auto-extend TTL on hit |
TOUCH_TTL |
00:10:00 |
Extension amount |
L1_SLIDING_EXPIRATION |
false |
Sliding vs Absolute |
VALKEY_RETRY_COUNT |
2 |
Max retry attempts |
VALKEY_RETRY_BASE_DELAY_MS |
200 |
Base retry delay |
VALKEY_CB_FAILURES |
5 |
Circuit breaker threshold |
VALKEY_CB_DURATION_SEC |
30 |
Circuit breaker duration |
ENABLE_L1 |
true |
Enable L1 |
ENABLE_L2 |
true |
Enable L2 |
ENABLE_EXTERNAL |
false |
Enable L3 |
DI methods
| Method | Valida | Uso |
|---|---|---|
AddHellnetCache() |
✅ VALKEY_CONNECTION + VALKEY_PASSWORD |
Produção |
AddHellnetCache(options) |
❌ (responsabilidade sua) | Testes, overrides |
Dependências
- .NET 10+
StackExchange.Redis— L2 Valkey providerPolly.Core— Resilience pipeline (retry + circuit breaker)Microsoft.Extensions.Caching.Memory— L1 memory providerMicrosoft.Extensions.DependencyInjection.AbstractionsMicrosoft.Extensions.Logging.Abstractions
Licença
Apache 2.0 © 2026 Hellnet
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Caching.Memory (>= 10.0.9)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
- Polly.Core (>= 8.7.0)
- StackExchange.Redis (>= 3.0.11)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.