Biblioteca .NET para cache com PostgreSQL
GitHub: BKlug.Extensions.Caching.PostgreSql
Inspirado em usar o PostgreSql como cache e depois da leitura do post da comunidade O Postgres consegue substituir o Redis como um cache? desenvolvi a biblioteca BKlug.Extensions.Caching.PostgreSql para .Net Core.
Essa lib oferece uma implementação completa de IDistributedCache
para .NET Core que utiliza tabelas UNLOGGED no PostgreSQL, sem precisar depender de serviços externos para cache em sua aplicação. A limpeza de itens expirados é realizado via pg_cron, eliminando a necessidade de threads de background no .NET e simplificando a manutenção. Também existe uma extensão de IDistributedCache
com métodos genéricos para trabalhar diretamente com objetos tipados, totalmente compatível com as interfaces nativas do .Net Core.
Principais benefícios de usar essa biblioteca
- Unificação de Infraestrutura: Se sua aplicação já utiliza PostgreSQL, manter o cache no mesmo banco reduz a complexidade operacional e aproveita recursos existentes.
- Performance Elevada: Tabelas UNLOGGED e uso de autovacuum.
- Limpeza Automática: Com pg_cron, itens expirados são removidos sem código adicional na aplicação, mantendo a tabela enxuta.
- Implementação Completa de IDistributedCache: Suporta métodos síncronos e assíncronos de
Get
,Set
,Refresh
eRemove
. - Extensões para Objetos Tipados: Métodos genéricos (
SetAsync<T>
,GetAsync<T>
,TryGetValueAsync<T>
,GetOrCreateAsync<T>
)..
Configuração e Integração
1. Registro de Serviço
Simples
services.AddDistributedPostgreSqlCache(options =>
{
options.ConnectionString = "Host=localhost;Database=cache;Username=postgres;Password=senha";
});
Avançada
services.AddDistributedPostgreSqlCache(options =>
{
options.ConnectionString = "Host=localhost;Database=cache;Username=postgres;Password=senha";
options.SchemaName = "cache";
options.TableName = "cache_items";
options.InitializeSchema = true;
options.CronSchedule = "*/5 * * * *";
options.MinPoolSize = 2;
options.MaxPoolSize = 50;
options.ConnectionLifetime = 600;
options.CommandTimeout = 60;
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(30);
options.UpdateOnGetCacheItem = false;
options.ReadOnlyMode = false;
});
Exemplos Práticos
Interface Padrão IDistributedCache
// Armazenar string
await _cache.SetStringAsync("greeting", "Olá Mundo",
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });
// Recuperar string
string saudacao = await _cache.GetStringAsync("greeting");
// Armazenar binário
await _cache.SetAsync("dados-bin", new byte[]{1,2,3,4,5});
// Recuperar binário
byte[]? dados = await _cache.GetAsync("dados-bin");
Extensões Aprimoradas
// Armazenar objeto tipado
await _cache.SetAsync("user:42", usuario, TimeSpan.FromHours(1));
// Recuperar objeto tipado
User? u = await _cache.GetAsync<User>("user:42");
// GetOrCreate
var prod = _cache.GetOrCreate("prod:1", () => CarregarProdutoDoBanco(), options);
Inicialização Manual do Esquema
Se InitializeSchema = false
, execute este script SQL manualmente:
-- Create schema and table
CREATE SCHEMA IF NOT EXISTS cache;
CREATE UNLOGGED TABLE IF NOT EXISTS cache.cache_items
(
id TEXT NOT NULL PRIMARY KEY,
value BYTEA,
expires_at_time TIMESTAMPTZ,
sliding_expiration_seconds DOUBLE PRECISION,
absolute_expiration TIMESTAMPTZ
)
WITH (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_analyze_scale_factor = 0.005
);
CREATE INDEX IF NOT EXISTS idx_cache_items_expires
ON cache.cache_items(expires_at_time)
WHERE expires_at_time IS NOT NULL;
-- Create function to delete expired items
CREATE OR REPLACE FUNCTION cache.delete_expired_cache_items()
RETURNS void LANGUAGE sql AS $$
DELETE FROM cache.cache_items
WHERE expires_at_time <= NOW();
$$;
-- Schedule the cleanup job (requires pg_cron extension)
-- Make sure pg_cron extension is installed first: CREATE EXTENSION IF NOT EXISTS pg_cron;
SELECT cron.schedule(
'cache_delete_expired',
'*/1 * * * *', -- Run every minute (cron format: minute hour day month weekday)
$$SELECT cache.delete_expired_cache_items()$$
);
Toda contribuição ou feedback da comunidade é muito bem-vinda para aprimorar o uso desta biblioteca!