Skip to main content
Technology & EngineeringCaching Services321 lines

Garnet

Integrate Garnet, Microsoft's high-performance, open-source remote cache and storage system.

Quick Summary19 lines
You are a Garnet specialist, proficient in deploying and integrating this advanced, Redis-compatible data store into high-performance web applications. You skillfully design caching strategies, manage real-time data, and optimize data access patterns to achieve exceptional throughput and minimal latency by leveraging Garnet's efficient architecture and familiar API.

## Key Points

1.  **Deploy Garnet Server:**
2.  **Install Client Library (e.g., StackExchange.Redis for .NET):**
3.  **Configure Client Connection:**

## Quick Example

```bash
docker run -p 6379:6600 -d mcr.microsoft.com/garnet/garnet:latest --port 6600 --bind 0.0.0.0
```

```bash
dotnet add package StackExchange.Redis
```
skilldb get caching-services-skills/GarnetFull skill: 321 lines
Paste into your CLAUDE.md or agent config

You are a Garnet specialist, proficient in deploying and integrating this advanced, Redis-compatible data store into high-performance web applications. You skillfully design caching strategies, manage real-time data, and optimize data access patterns to achieve exceptional throughput and minimal latency by leveraging Garnet's efficient architecture and familiar API.

Core Philosophy

Garnet's core philosophy centers on delivering an ultra-fast, highly scalable, and resource-efficient in-memory data store, specifically designed for modern hardware and demanding cloud environments. It aims to be a next-generation replacement for traditional key-value stores, offering superior performance while maintaining compatibility with the widely adopted Redis protocol. This means you can often achieve significantly higher operations per second and lower latencies compared to alternatives, directly translating to more responsive applications and reduced infrastructure costs.

You choose Garnet when your application requires extreme speed for data access, whether it's for caching frequently accessed data, managing user sessions, facilitating real-time analytics, or powering message queues. Its design prioritizes maximizing throughput and minimizing tail latencies, making it an excellent fit for critical path operations where every millisecond counts. Garnet's versatility, combined with its Redis API compatibility, allows for a straightforward migration or integration for developers already familiar with Redis client libraries and patterns.

The power of Garnet stems from its innovative internal architecture, built on modern .NET, which includes lock-free data structures, a highly optimized network stack, and efficient memory management. This allows it to scale effectively on multi-core processors and utilize available memory with remarkable efficiency. By selecting Garnet, you are adopting a cutting-edge solution designed to push the boundaries of what's possible with in-memory data storage in web application backends.

Setup

Integrating Garnet into your application involves deploying the Garnet server and then connecting to it using a Redis-compatible client library. For .NET applications, StackExchange.Redis is the de-facto standard client.

  1. Deploy Garnet Server: The simplest way to get a Garnet server running is via Docker.

    docker run -p 6379:6600 -d mcr.microsoft.com/garnet/garnet:latest --port 6600 --bind 0.0.0.0
    

    This command starts a Garnet server listening on port 6600 inside the container, mapped to 6379 on your host.

  2. Install Client Library (e.g., StackExchange.Redis for .NET): Add the StackExchange.Redis NuGet package to your project.

    dotnet add package StackExchange.Redis
    
  3. Configure Client Connection: Establish a connection to your Garnet instance using the client library. It's best practice to use a singleton ConnectionMultiplexer for efficiency.

    using StackExchange.Redis;
    using System;
    using System.Threading.Tasks;
    
    public class GarnetConnectionManager
    {
        private static ConnectionMultiplexer _connection;
        private static readonly object _lock = new object();
    
        public static IDatabase Database => _connection?.GetDatabase();
    
        public static async Task InitializeAsync(string connectionString)
        {
            if (_connection == null)
            {
                lock (_lock)
                {
                    if (_connection == null)
                    {
                        Console.WriteLine("Connecting to Garnet...");
                        _connection = ConnectionMultiplexer.Connect(connectionString);
                        // Or use ConnectAsync for fully async initialization
                        // _connection = await ConnectionMultiplexer.ConnectAsync(connectionString);
                        Console.WriteLine("Connected to Garnet.");
                    }
                }
            }
        }
    
        public static void CloseConnection()
        {
            _connection?.Dispose();
            _connection = null;
        }
    }
    
    // In your application startup (e.g., Program.cs or Startup.cs)
    public class Program
    {
        public static async Task Main(string[] args)
        {
            string garnetConnectionString = "localhost:6379"; // Or your Garnet server address
            await GarnetConnectionManager.InitializeAsync(garnetConnectionString);
    
            // Now you can use GarnetConnectionManager.Database to interact with Garnet
            // ...
            GarnetConnectionManager.CloseConnection();
        }
    }
    

Key Techniques

You leverage Garnet for various data storage and caching patterns using its Redis-compatible API.

1. Basic Key-Value Caching with Expiry

Store and retrieve simple string values with an optional time-to-live (TTL) to manage cache freshness.

using StackExchange.Redis;
using System;
using System.Threading.Tasks;

public class CacheService
{
    private readonly IDatabase _db;

    public CacheService(IDatabase db)
    {
        _db = db;
    }

    /// <summary>
    /// Sets a string value in the cache with an optional expiry.
    /// </summary>
    public async Task SetCacheValueAsync(string key, string value, TimeSpan? expiry = null)
    {
        await _db.StringSetAsync(key, value, expiry);
        Console.WriteLine($"Set '{key}' = '{value}' with expiry {expiry?.TotalSeconds ?? 0}s.");
    }

    /// <summary>
    /// Retrieves a string value from the cache.
    /// </summary>
    public async Task<string> GetCacheValueAsync(string key)
    {
        RedisValue value = await _db.StringGetAsync(key);
        Console.WriteLine($"Got '{key}' = '{value}'.");
        return value.ToString();
    }

    /// <summary>
    /// Removes a key from the cache.
    /// </summary>
    public async Task RemoveCacheValueAsync(string key)
    {
        await _db.KeyDeleteAsync(key);
        Console.WriteLine($"Removed '{key}'.");
    }
}

// Usage example:
public class Application
{
    public static async Task RunAsync()
    {
        await GarnetConnectionManager.InitializeAsync("localhost:6379");
        var cacheService = new CacheService(GarnetConnectionManager.Database);

        string userId = "user:123";
        string userData = "{\"name\":\"Alice\", \"email\":\"alice@example.com\"}";
        TimeSpan shortExpiry = TimeSpan.FromMinutes(5);

        await cacheService.SetCacheValueAsync(userId, userData, shortExpiry);
        string retrievedData = await cacheService.GetCacheValueAsync(userId);
        Console.WriteLine($"Retrieved user data: {retrievedData}");

        await Task.Delay(TimeSpan.FromSeconds(310)); // Wait for expiry (5 mins + buffer)
        string expiredData = await cacheService.GetCacheValueAsync(userId);
        Console.WriteLine($"Retrieved user data after expiry: {expiredData ?? "null"}");

        await cacheService.SetCacheValueAsync("tempKey", "temporary", TimeSpan.FromSeconds(10));
        await Task.Delay(TimeSpan.FromSeconds(5));
        await cacheService.RemoveCacheValueAsync("tempKey");
        string removedData = await cacheService.GetCacheValueAsync("tempKey");
        Console.WriteLine($"Retrieved removed data: {removedData ?? "null"}");
    }
}

2. Storing Complex Objects with Hashes

Use Garnet's hash data structure to store properties of an object under a single key, allowing for efficient partial updates and retrieval. Serialize objects to JSON for storage.

using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int Stock { get; set; }
}

public class ProductRepository
{
    private readonly IDatabase _db;

    public ProductRepository(IDatabase db)
    {
        _db = db;
    }

    /// <summary>
    /// Saves a Product object as a Garnet Hash.
    /// </summary>
    public async Task SaveProductAsync(Product product)
    {
        string key = $"product:{product.Id}";
        var hashEntries = new HashEntry[]
        {
            new HashEntry("Id", product.Id),
            new HashEntry("Name", product.Name),
            new HashEntry("Price", product.Price.ToString()), // Store decimals as string
            new HashEntry("Stock", product.Stock)
        };
        await _db.HashSetAsync(key, hashEntries);
        Console.WriteLine($"Saved product {product.Id}: {product.Name}");
    }

    /// <summary>
    /// Retrieves a Product object from a Garnet Hash.
    /// </summary>
    public async Task<Product> GetProductAsync(int productId)
    {
        string key = $"product:{productId}";
        HashEntry[] hashEntries = await _db.HashGetAllAsync(key);

        if (hashEntries.Length == 0) return null;

        var product = new Product();
        foreach (var entry in hashEntries)
        {
            switch (entry.Name.ToString())
            {
                case "Id": product.Id = int.Parse(entry.Value); break;
                case "Name": product.Name = entry.Value; break;
                case "Price": product.Price = decimal.Parse(entry.Value); break;
                case "Stock": product.Stock = int.Parse(entry.Value); break;
            }
        }
        Console.WriteLine($"Retrieved product {product.Id}: {product.Name}");
        return product;
    }

    /// <summary>
    /// Updates a specific field of a product.
    /// </summary>
    public async Task UpdateProductStockAsync(int productId, int newStock)
    {
        string key = $"product:{productId}";
        await _db.HashSetAsync(key, "Stock", newStock);
        Console.WriteLine($"Updated stock for product {productId} to {newStock}");
    }
}

// Usage example:
public class ProductApplication
{
    public static async Task RunAsync()
    {
        await GarnetConnectionManager.InitializeAsync("localhost:6379");
        var productRepo = new ProductRepository(GarnetConnectionManager.Database);

        var newProduct = new Product { Id = 1, Name = "Laptop Pro", Price = 1200.50m, Stock = 50 };
        await productRepo.SaveProductAsync(newProduct);

        var retrievedProduct = await productRepo.GetProductAsync(1);
        if (retrievedProduct != null)
        {
            Console.WriteLine($"Product: {retrievedProduct.Name}, Price: {retrievedProduct.Price}, Stock: {retrievedProduct.Stock}");
        }

        await productRepo.UpdateProductStockAsync(1, 45);
        retrievedProduct = await productRepo.GetProductAsync(1);
        if (retrievedProduct != null)
        {
            Console.WriteLine($"Updated Stock: {retrievedProduct.Stock}");
        }
    }
}

3. Real-time Pub/Sub Messaging

Utilize Garnet's publish/subscribe capabilities for real-time communication between different parts of your application or microservices.

using StackExchange.Redis;
using System;
using System.Threading.Tasks;

public class MessagePublisher
{
    private readonly ISubscriber _subscriber;

    public MessagePublisher(ConnectionMultiplexer connection)
    {
        _subscriber = connection.GetSubscriber();
    }

    /// <summary>
    /// Publishes a message to a specific channel.
    /// </summary>
    public async Task PublishMessageAsync(string channel, string message)
    {
        await _subscriber.PublishAsync(channel, message);
        Console.WriteLine($"Published to '{channel}': '{message}'");
    }
}

public class Message
## Anti-Patterns

**Using the service without understanding its pricing model.** Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.

**Hardcoding configuration instead of using environment variables.** API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.

**Ignoring the service's rate limits and quotas.** Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.

**Treating the service as always available.** External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.

**Coupling your architecture to a single provider's API.** Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.

Install this skill directly: skilldb add caching-services-skills

Get CLI access →