PostgreSQL-backed Distributed Caching for .NET

Reuse the database you already trust to get durable, cross-instance caching without adding new infrastructure

From the maintainer of Extensions.Caching.PostgreSql (900K+ downloads)

Built on Proven Foundations

900K+
Downloads
6+
Years Maintained
20+
Releases

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.

What's New in GlacialCache

  • Advanced Serialization: MemoryPack for blazing-fast performance, JSON for compatibility, or custom serializers
  • Batch Operations: GetMultipleAsync and SetMultipleAsync for efficient multi-key operations
  • Type-Safe APIs: Generic GetEntryAsync<T> and SetEntryAsync<T> with automatic serialization
  • Enterprise Features: Azure Managed Identity, advanced resilience patterns, and rich diagnostics
  • Modern .NET: Built for .NET 8, 9, and 10 with async-first design

Migrating from Extensions.Caching.PostgreSql? GlacialCache maintains full IDistributedCache compatibility. Simply swap the package and registration—your existing code continues to work!

Why Choose GlacialCache?

Skip the complexity of managing separate caching infrastructure

Reuse PostgreSQL, Skip New Infrastructure

No Redis cluster to deploy or operate. GlacialCache leverages the PostgreSQL you already run, saving you operational overhead and costs.

Drop-in IDistributedCache

Works seamlessly with standard ASP.NET Core patterns. Full IDistributedCache implementation for PostgreSQL—just swap the registration and your existing code works instantly.

Flexible Serialization Options

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.

Powerful Batch Operations

GetMultipleAsync and SetMultipleAsync enable efficient bulk operations in a single database round-trip. Dramatically reduce latency for multi-key scenarios.

Typed Sugar Methods

Generic GetEntryAsync<T> and SetEntryAsync<T> provide automatic serialization/deserialization with full type safety. No more manual JSON conversion boilerplate.

Durable, Cross-Instance Cache

Cache entries are stored in PostgreSQL and shared across all app instances, surviving restarts and deployments with automatic expiration handling.

Production-Ready Performance

Async-first with Npgsql connection pooling, optimized SQL, and efficient storage of strings, bytes, and complex objects.

Built for Reliability

Background cleanup, manager election, resilience policies, and structured logging are included from day one.

Cloud-Native & Enterprise

Azure Managed Identity support for passwordless connections, rich logging, and configurable timeouts for production workloads.

Get Started in 3 Simple Steps

1

Install the Package

dotnet add package GlacialCache.PostgreSQL
2

Register in Program.cs

using GlacialCache.PostgreSQL;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGlacialCachePostgreSQL(
    "Host=localhost;Database=myapp;Username=postgres;Password=mypassword");

var app = builder.Build();
3

Use IDistributedCache

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;
    }
}
4

Unlock Advanced Features

// 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;
});

Advanced Features

Go beyond standard caching with powerful serialization, batch operations, and type-safe APIs

Flexible Serialization

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 serializer

Batch Operations

Dramatically 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
}

Typed Operations

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);
    }
}

How Does GlacialCache Compare?

FeatureGlacialCache.PostgreSQLIMemoryCacheRedis-based Cache
Storage LocationPostgreSQL tableApplication memoryDedicated Redis instance
Shared Across Instances
Survives Restarts
(Depends on config)
NO Extra Infrastructure
(uses existing PostgreSQL instance)

(Redis server)
Operational ComplexityLow (reuses DB)LowMedium-High
Best When...You run PostgreSQL & want durable cachingSingle-instance or ephemeralUltra-high throughput needed

Documentation

Everything you need to get from installation to production tuning

What's New

Latest updates and release notes

Ready to Simplify Your Caching?

Join developers who are leveraging PostgreSQL for distributed caching