Isaac.

caching

Redis Caching Advanced

Master advanced Redis caching patterns and strategies.

By Emem IsaacJune 30, 20244 min read
#redis#caching#performance#data structures
Share:

A Simple Analogy

Redis is like a super-fast notepad for your application. Instead of searching a library (database), you grab a sticky note with the answer.


Why Redis?

  • Speed: In-memory, microsecond latency
  • Patterns: Hash, set, list, sorted set, streams
  • Expiration: Auto-delete old data
  • Pub/Sub: Real-time messaging
  • Transactions: Atomic operations

Basic Operations

var redis = ConnectionMultiplexer.Connect("localhost:6379");
var db = redis.GetDatabase();

// String
db.StringSet("user:1:name", "Alice");
var name = db.StringGet("user:1:name");

// Hash (related fields)
db.HashSet("user:1", new HashEntry[]
{
    new("name", "Alice"),
    new("email", "alice@example.com"),
    new("age", "30")
});

var all = db.HashGetAll("user:1");
var email = db.HashGet("user:1", "email");

// List
db.ListPush("notifications:user:1", "New message");
db.ListPush("notifications:user:1", "Order shipped");
var latest = db.ListRange("notifications:user:1", 0, 9);

// Set (unique items, fast membership test)
db.SetAdd("followers:alice", "bob");
db.SetAdd("followers:alice", "charlie");
var hasFollower = db.SetContains("followers:alice", "bob");
var allFollowers = db.SetMembers("followers:alice");

// Sorted Set (ordered by score)
db.SortedSetAdd("leaderboard", new SortedSetEntry[]
{
    new("alice", 1000),
    new("bob", 950),
    new("charlie", 890)
});

var top3 = db.SortedSetRangeByRankWithScores("leaderboard", 0, 2, Order.Descending);

Cache-Aside Pattern

public class UserRepository
{
    private readonly IDistributedCache _cache;
    private readonly IDatabase _db;
    
    public async Task<User> GetUserAsync(int id)
    {
        var cacheKey = $"user:{id}";
        
        // Try cache first
        var cached = await _cache.GetStringAsync(cacheKey);
        if (!string.IsNullOrEmpty(cached))
            return JsonSerializer.Deserialize<User>(cached);
        
        // Not in cache, get from database
        var user = await _db.Users.FindAsync(id);
        if (user != null)
        {
            // Store in cache for 1 hour
            await _cache.SetStringAsync(
                cacheKey,
                JsonSerializer.Serialize(user),
                new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
                });
        }
        
        return user;
    }
}

Cache Invalidation

public class UserService
{
    private readonly IDistributedCache _cache;
    
    public async Task UpdateUserAsync(User user)
    {
        // Update database
        await _db.SaveAsync(user);
        
        // Invalidate cache
        await _cache.RemoveAsync($"user:{user.Id}");
        await _cache.RemoveAsync($"user-email:{user.Email}");
    }
    
    // Use tags for related cache entries
    public async Task InvalidateUserRelatedAsync(int userId)
    {
        var pattern = $"user:{userId}:*";
        
        // Remove all related keys
        var server = _redis.GetServer(_redis.GetEndPoints().First());
        var keys = server.Keys(pattern: pattern);
        
        foreach (var key in keys)
        {
            await _cache.RemoveAsync(key.ToString());
        }
    }
}

Pub/Sub Messaging

// Subscriber
var subscriber = redis.GetSubscriber();

await subscriber.SubscribeAsync("orders", (channel, message) =>
{
    var order = JsonSerializer.Deserialize<Order>(message.ToString());
    ProcessOrder(order);
});

// Publisher
var orders = redis.GetSubscriber();
var newOrder = new Order { Id = 123, Total = 99.99 };
await orders.PublishAsync("orders", JsonSerializer.Serialize(newOrder));

// Multiple channels
await subscriber.SubscribeAsync(new[]
{
    new ChannelMessageQueue("orders"),
    new ChannelMessageQueue("notifications"),
    new ChannelMessageQueue("events")
}, (channel, message) =>
{
    HandleMessage(channel, message);
});

Streams (Event Log)

var db = redis.GetDatabase();

// Add to stream
var eventId = await db.StreamAddAsync("events", new NameValueEntry[]
{
    new("action", "order-created"),
    new("order-id", "123"),
    new("timestamp", DateTime.UtcNow.ToString("O"))
});

// Read stream
var entries = await db.StreamRangeAsync("events", "-", "+");  // All entries
var lastN = await db.StreamRangeAsync("events", "-", "+", take: 10);  // Last 10

// Consumer groups
await db.StreamCreateConsumerGroupAsync("events", "order-service", StreamPosition.NewMessages);

var message = await db.StreamReadGroupAsync(
    "events",
    "order-service",
    "consumer-1",
    count: 1);

Distributed Lock

public class RedisLock : IDisposable
{
    private readonly IDatabase _db;
    private readonly string _lockKey;
    private readonly string _lockValue;
    
    public RedisLock(IDatabase db, string key, TimeSpan expiration)
    {
        _db = db;
        _lockKey = key;
        _lockValue = Guid.NewGuid().ToString();
        
        var acquired = _db.StringSet(_lockKey, _lockValue, expiration, When.NotExists);
        if (!acquired)
            throw new InvalidOperationException("Could not acquire lock");
    }
    
    public void Dispose()
    {
        var tx = _db.CreateTransaction();
        tx.AddCondition(Condition.StringEqual(_lockKey, _lockValue));
        tx.StringDeleteAsync(_lockKey);
        tx.Execute();
    }
}

// Usage
using (new RedisLock(db, "user:1:update", TimeSpan.FromSeconds(5)))
{
    // Only one process can enter here at a time
    var user = await GetUserAsync(1);
    user.Balance -= 100;
    await SaveUserAsync(user);
}

Best Practices

  1. Use appropriate data structures: Hash for objects, Set for tags
  2. Set expiration: Prevent unbounded memory
  3. Monitor memory: Redis stores everything in RAM
  4. Use pipelining: Batch commands for efficiency
  5. Handle connection failures: Graceful degradation

Related Concepts

  • Memcached (simpler alternative)
  • Cache warming strategies
  • Cache stampede prevention
  • Message queues in Redis

Summary

Redis provides blazing-fast in-memory caching with advanced data structures. Master cache-aside patterns, pub/sub messaging, and streams to build responsive, scalable systems.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours