Reuse the database you already trust to get durable, cross-instance caching without adding new infrastructure
GlacialCache is the modern evolution of Extensions.Caching.PostgreSql, a battle-tested PostgreSQL caching library trusted by hundreds of thousands of developers and production applications since 2019.
After years of maintaining the original library and gathering feedback from the community, we built GlacialCache from the ground up to address modern .NET requirements and deliver the features developers requested most.
Migrating from Extensions.Caching.PostgreSql? GlacialCache maintains full IDistributedCache compatibility. Simply swap the package and registration—your existing code continues to work!
Skip the complexity of managing separate caching infrastructure
No Redis cluster to deploy or operate. GlacialCache leverages the PostgreSQL you already run, saving you operational overhead and costs.
Works seamlessly with standard ASP.NET Core patterns. Full IDistributedCache implementation for PostgreSQL—just swap the registration and your existing code works instantly.
Choose MemoryPack for maximum performance, System.Text.Json for compatibility, or implement your own ICacheEntrySerializer for custom needs. Strings and byte arrays are always optimized.
GetMultipleAsync and SetMultipleAsync enable efficient bulk operations in a single database round-trip. Dramatically reduce latency for multi-key scenarios.
Generic GetEntryAsync<T> and SetEntryAsync<T> provide automatic serialization/deserialization with full type safety. No more manual JSON conversion boilerplate.
Cache entries are stored in PostgreSQL and shared across all app instances, surviving restarts and deployments with automatic expiration handling.
Async-first with Npgsql connection pooling, optimized SQL, and efficient storage of strings, bytes, and complex objects.
Background cleanup, manager election, resilience policies, and structured logging are included from day one.
Azure Managed Identity support for passwordless connections, rich logging, and configurable timeouts for production workloads.
dotnet add package GlacialCache.PostgreSQLusing GlacialCache.PostgreSQL;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGlacialCachePostgreSQL(
"Host=localhost;Database=myapp;Username=postgres;Password=mypassword");
var app = builder.Build();public class ProductService
{
private readonly IDistributedCache _cache;
public async Task<Product?> GetProductAsync(int id)
{
var key = $"product:{id}";
var cached = await _cache.GetStringAsync(key);
if (cached is not null)
return JsonSerializer.Deserialize<Product>(cached);
var product = await _repository.GetProductAsync(id);
await _cache.SetStringAsync(key, JsonSerializer.Serialize(product));
return product;
}
}// Batch operations for efficiency
var keys = new[] { "product:1", "product:2", "product:3" };
var results = await cache.GetMultipleAsync(keys);
// Typed operations with automatic serialization
await cache.SetEntryAsync("user:123", user, new() {
SlidingExpiration = TimeSpan.FromMinutes(30)
});
var cachedUser = await cache.GetEntryAsync<User>("user:123");
// Configure serialization (MemoryPack, JSON, or Custom)
builder.Services.AddGlacialCachePostgreSQL(options => {
options.Cache.Serializer = SerializerType.JsonBytes;
});Go beyond standard caching with powerful serialization, batch operations, and type-safe APIs
Choose the serialization strategy that fits your needs: high-performance MemoryPack (default), standards-based System.Text.Json, or implement your own custom serializer.
// Option 1: MemoryPack (Default - Highest Performance)
builder.Services.AddGlacialCachePostgreSQL(options => {
options.Connection.ConnectionString = connectionString;
options.Cache.Serializer = SerializerType.MemoryPack; // Default
});
// Option 2: JSON with System.Text.Json (Microsoft)
builder.Services.AddGlacialCachePostgreSQL(options => {
options.Connection.ConnectionString = connectionString;
options.Cache.Serializer = SerializerType.JsonBytes;
});
// Option 3: Custom Serializer
public class MyCustomSerializer : ICacheEntrySerializer
{
public byte[] Serialize<T>(T value) where T : notnull
{
// Your custom serialization logic
}
public T Deserialize<T>(byte[] data) where T : notnull
{
// Your custom deserialization logic
}
}
builder.Services.AddGlacialCachePostgreSQL(options => {
options.Cache.Serializer = SerializerType.Custom;
options.Cache.CustomSerializerType = typeof(MyCustomSerializer);
});
// Note: Strings and byte arrays are always optimized regardless of serializerDramatically reduce database round-trips by fetching or setting multiple cache entries in a single operation. Perfect for high-throughput scenarios.
// Inject IGlacialCache (extends IDistributedCache)
public class ProductService
{
private readonly IGlacialCache _cache;
public ProductService(IGlacialCache cache) => _cache = cache;
// Batch GET - Single database round-trip for multiple keys
public async Task<List<Product>> GetProductsAsync(int[] ids)
{
var keys = ids.Select(id => $"product:{id}");
var results = await _cache.GetMultipleAsync(keys);
return results.Values
.Where(v => v != null)
.Select(v => JsonSerializer.Deserialize<Product>(v))
.ToList();
}
// Batch SET - Efficient bulk insertion
public async Task CacheProductsAsync(List<Product> products)
{
var entries = products.ToDictionary(
p => $"product:{p.Id}",
p => (
value: Encoding.UTF8.GetBytes(JsonSerializer.Serialize(p)),
options: new DistributedCacheEntryOptions {
SlidingExpiration = TimeSpan.FromMinutes(30)
}
)
);
await _cache.SetMultipleAsync(entries);
}
// Also available: RemoveMultipleAsync, RefreshMultipleAsync
}Skip manual serialization with generic methods that handle type conversion automatically. Full type safety with IntelliSense support.
// Inject IGlacialCache for advanced features
public class UserService
{
private readonly IGlacialCache _cache;
public UserService(IGlacialCache cache) => _cache = cache;
// Typed GET with automatic deserialization
public async Task<User?> GetUserAsync(int userId)
{
var entry = await _cache.GetEntryAsync<User>($"user:{userId}");
return entry?.Value; // Strongly typed!
}
// Typed SET with automatic serialization
public async Task CacheUserAsync(User user)
{
await _cache.SetEntryAsync($"user:{user.Id}", user, new() {
SlidingExpiration = TimeSpan.FromMinutes(30),
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2)
});
}
// Batch typed operations
public async Task<List<User>> GetUsersAsync(int[] userIds)
{
var keys = userIds.Select(id => $"user:{id}");
var entries = await _cache.GetMultipleEntriesAsync<User>(keys);
return entries.Values
.Where(e => e?.Value != null)
.Select(e => e.Value)
.ToList();
}
// Set multiple typed entries
public async Task CacheUsersAsync(List<User> users)
{
var entries = users.ToDictionary(
u => $"user:{u.Id}",
u => (value: u, options: (DistributedCacheEntryOptions?)new() {
SlidingExpiration = TimeSpan.FromMinutes(30)
})
);
await _cache.SetMultipleEntriesAsync(entries);
}
}| Feature | GlacialCache.PostgreSQL | IMemoryCache | Redis-based Cache |
|---|---|---|---|
| Storage Location | PostgreSQL table | Application memory | Dedicated Redis instance |
| Shared Across Instances | |||
| Survives Restarts | (Depends on config) | ||
| NO Extra Infrastructure | (uses existing PostgreSQL instance) | (Redis server) | |
| Operational Complexity | Low (reuses DB) | Low | Medium-High |
| Best When... | You run PostgreSQL & want durable caching | Single-instance or ephemeral | Ultra-high throughput needed |
Everything you need to get from installation to production tuning
Join developers who are leveraging PostgreSQL for distributed caching