{"id":30453671,"url":"https://github.com/ersintarhan/rediskit","last_synced_at":"2026-01-20T16:53:06.320Z","repository":{"id":310139971,"uuid":"1038856062","full_name":"ersintarhan/RedisKit","owner":"ersintarhan","description":"Production-ready Redis toolkit for .NET 9 with advanced caching, pub/sub, and streaming features","archived":false,"fork":false,"pushed_at":"2025-08-16T02:03:21.000Z","size":6508,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-08-16T02:41:08.139Z","etag":null,"topics":["cache","circuit-breaker","csharp","dotnet","high-performance","messagepack","net9","pubsub","redis","streams"],"latest_commit_sha":null,"homepage":"https://ersintarhan.github.io/RedisKit","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ersintarhan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-16T00:24:50.000Z","updated_at":"2025-08-16T02:03:07.000Z","dependencies_parsed_at":"2025-08-16T02:51:15.609Z","dependency_job_id":null,"html_url":"https://github.com/ersintarhan/RedisKit","commit_stats":null,"previous_names":["ersintarhan/rediskit"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/ersintarhan/RedisKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ersintarhan%2FRedisKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ersintarhan%2FRedisKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ersintarhan%2FRedisKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ersintarhan%2FRedisKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ersintarhan","download_url":"https://codeload.github.com/ersintarhan/RedisKit/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ersintarhan%2FRedisKit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271755402,"owners_count":24815397,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-23T02:00:09.327Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","circuit-breaker","csharp","dotnet","high-performance","messagepack","net9","pubsub","redis","streams"],"created_at":"2025-08-23T16:01:14.756Z","updated_at":"2026-01-20T16:53:06.314Z","avatar_url":"https://github.com/ersintarhan.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RedisKit\n\n[![Build Status](https://github.com/ersintarhan/RedisKit/workflows/CI/badge.svg)](https://github.com/ersintarhan/RedisKit/actions)\n[![Code Quality](https://img.shields.io/badge/qodana-65%25%20coverage-brightgreen)](https://qodana.cloud/projects/ersintarhan-RedisKit)\n[![Tests](https://img.shields.io/badge/tests-645%20passed-brightgreen)](https://github.com/ersintarhan/RedisKit/actions)\n[![Coverage](https://img.shields.io/badge/coverage-65%25-brightgreen)](#testing)\n[![.NET](https://img.shields.io/badge/.NET-9.0-blue)](https://dotnet.microsoft.com/)\n[![NuGet](https://img.shields.io/nuget/v/RedisKit.svg)](https://www.nuget.org/packages/RedisKit/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Performance](https://img.shields.io/badge/performance-95%25%20faster-success)](#performance-benchmarks)\n\nA production-ready, enterprise-grade Redis library for .NET 9 with advanced caching, pub/sub, and streaming features.\n\n## 🚀 Features\n\n### Core Features\n\n- **Caching**: Generic Get, Set, Delete operations with TTL support\n- **Batch Operations**: GetMany and SetMany for improved performance\n- **Key Prefixing**: Support for cache key prefixes\n- **Pub/Sub**: Type-safe publishing and subscribing with advanced pattern matching\n- **Streaming**: Redis Streams support with consumer groups and retry mechanisms\n- **Multiple Serializers**: JSON, MessagePack support\n- **Dependency Injection**: Full support with .NET DI container\n- **High Performance Logging**: Source generator based logging with EventId support\n- **Async/Await**: Full async/await support with CancellationToken\n\n### Redis 7.x Features (NEW!)\n\n- **🚀 Redis Functions**: Server-side scripting with Lua (replacement for Redis Scripts)\n- **📡 Sharded Pub/Sub**: Scalable pub/sub across cluster shards\n- **🔧 Function Library Builder**: Fluent API for creating Redis function libraries\n- **📊 Array Return Types**: Full support for array results from Redis functions\n- **🎯 Native Sharded Channel Support**: Using StackExchange.Redis's RedisChannel.Sharded() API\n\n### Enterprise Features\n\n- **🔒 Distributed Locking**: Redis-based distributed locking with auto-renewal\n- **🛡️ Redis Sentinel Support**: High availability with automatic failover (NEW!)\n- **🔄 Circuit Breaker Pattern**: Automatic failure detection and recovery\n- **📈 Advanced Retry Strategies**: Multiple backoff strategies (Exponential, Decorrelated Jitter, etc.)\n- **🏥 Health Monitoring**: Automatic health checks with auto-reconnection\n- **🎯 Pattern Matching**: Redis glob pattern support (`*`, `?`, `[abc]`, `[^abc]`, `[a-z]`)\n- **🧹 Memory Leak Prevention**: Automatic cleanup of inactive handlers\n- **📊 Statistics \u0026 Monitoring**: Built-in metrics for subscriptions and connections\n- **⚡ High Performance**: Optimized with concurrent collections and minimal allocations\n- **🔐 Thread Safety**: All operations are thread-safe\n- **🚀 Lua Script Optimization**: 90-95% performance improvement for batch operations\n- **📝 Source-Generated Logging**: Zero-allocation high-performance logging\n\n### Performance \u0026 Memory Optimizations\n\n- **💾 Object Pooling**: ArrayPool and ObjectPool for reduced GC pressure\n- **🌊 Streaming API**: IAsyncEnumerable for processing large datasets without memory overhead\n- **🎛️ Dynamic Parallelism**: CPU-aware parallel processing (auto-scales with cores)\n- **📦 Smart Batching**: Size-based strategy selection for optimal performance\n- **⚡ Inline Optimizations**: AggressiveInlining for hot paths\n- **🔄 Zero-Copy Operations**: Minimal allocations in critical paths\n- **🚀 ValueTask Support**: Reduced heap allocations in hot paths with ValueTask\n- **📊 Memory\u003cT\u003e \u0026 Span\u003cT\u003e**: Zero-allocation serialization with Memory\u003cbyte\u003e buffers\n- **🔗 Pipeline Batching**: ExecuteBatchAsync for multiple operations in single round-trip\n\n## 📦 Installation\n\nInstall the package via NuGet:\n\n```bash\ndotnet add package RedisKit\n```\n\n## 🎯 Quick Start\n\n### Minimal Setup\n\n```csharp\nusing RedisKit.Extensions;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add Redis services with minimal configuration\nbuilder.Services.AddRedisServices(options =\u003e\n{\n    options.ConnectionString = \"localhost:6379\";\n});\n\nvar app = builder.Build();\n\n// Use in your controllers or services\napp.MapGet(\"/cache/{key}\", async (string key, IRedisCacheService cache) =\u003e\n{\n    var value = await cache.GetAsync\u003cstring\u003e(key);\n    return value ?? \"Not found\";\n});\n\napp.Run();\n```\n\n## 👶 Getting Started - Hello Redis!\n\n### Your First Redis Cache\n\n```csharp\nusing RedisKit.Extensions;\nusing RedisKit.Interfaces;\n\n// 1. Setup - Add to your Program.cs\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddRedisServices(options =\u003e\n{\n    options.ConnectionString = \"localhost:6379\";\n});\n\nvar app = builder.Build();\n\n// 2. Simple String Cache\napp.MapPost(\"/hello/{name}\", async (string name, IRedisCacheService cache) =\u003e\n{\n    // Store a simple string\n    await cache.SetAsync($\"greeting:{name}\", $\"Hello, {name}!\", TimeSpan.FromMinutes(5));\n    return $\"Greeting saved for {name}\";\n});\n\napp.MapGet(\"/hello/{name}\", async (string name, IRedisCacheService cache) =\u003e\n{\n    // Retrieve the string\n    var greeting = await cache.GetAsync\u003cstring\u003e($\"greeting:{name}\");\n    return greeting ?? \"No greeting found\";\n});\n\napp.Run();\n```\n\n### Counter Example - Increment Values\n\n```csharp\npublic class CounterService\n{\n    private readonly IRedisCacheService _cache;\n    \n    public CounterService(IRedisCacheService cache)\n    {\n        _cache = cache;\n    }\n    \n    public async Task\u003cint\u003e IncrementVisitCountAsync(string page)\n    {\n        var key = $\"visits:{page}\";\n        \n        // Get current count\n        var currentCount = await _cache.GetAsync\u003cint?\u003e(key) ?? 0;\n        \n        // Increment and save\n        currentCount++;\n        await _cache.SetAsync(key, currentCount, TimeSpan.FromDays(30));\n        \n        return currentCount;\n    }\n}\n```\n\n### Simple User Session\n\n```csharp\npublic class SessionService\n{\n    private readonly IRedisCacheService _cache;\n    \n    public SessionService(IRedisCacheService cache)\n    {\n        _cache = cache;\n    }\n    \n    // Store user session\n    public async Task CreateSessionAsync(string sessionId, string userId, string userName)\n    {\n        var session = new UserSession \n        { \n            UserId = userId, \n            UserName = userName, \n            LoginTime = DateTime.UtcNow \n        };\n        \n        // Session expires in 20 minutes\n        await _cache.SetAsync($\"session:{sessionId}\", session, TimeSpan.FromMinutes(20));\n    }\n    \n    // Get user session\n    public async Task\u003cUserSession?\u003e GetSessionAsync(string sessionId)\n    {\n        return await _cache.GetAsync\u003cUserSession\u003e($\"session:{sessionId}\");\n    }\n    \n    // Extend session\n    public async Task ExtendSessionAsync(string sessionId)\n    {\n        var session = await GetSessionAsync(sessionId);\n        if (session != null)\n        {\n            // Reset expiration to 20 minutes\n            await _cache.ExpireAsync($\"session:{sessionId}\", TimeSpan.FromMinutes(20));\n        }\n    }\n}\n\npublic class UserSession\n{\n    public string UserId { get; set; }\n    public string UserName { get; set; }\n    public DateTime LoginTime { get; set; }\n}\n```\n\n## 🔧 Configuration\n\n### Basic Configuration\n\n```csharp\nservices.AddRedisServices(options =\u003e\n{\n    options.ConnectionString = \"localhost:6379\";\n    options.DefaultTtl = TimeSpan.FromHours(1);\n    options.CacheKeyPrefix = \"myapp:\";\n    options.Serializer = SerializerType.MessagePack; // or JSON\n});\n```\n\n### Redis Sentinel Configuration (High Availability)\n\n```csharp\nservices.AddRedisServices(options =\u003e\n{\n    // Configure Redis Sentinel for high availability and automatic failover\n    options.Sentinel = new SentinelOptions\n    {\n        Endpoints = new List\u003cstring\u003e \n        { \n            \"sentinel1:26379\",\n            \"sentinel2:26379\",\n            \"sentinel3:26379\"\n        },\n        ServiceName = \"mymaster\",\n        RedisPassword = \"your_redis_password\",\n        EnableFailoverHandling = true,\n        HealthCheckInterval = TimeSpan.FromSeconds(30)\n    };\n    \n    // Note: When using Sentinel, ConnectionString is not required\n    // RedisKit will automatically discover the master via Sentinel\n});\n```\n\n### Advanced Configuration\n\n```csharp\nservices.AddRedisServices(options =\u003e\n{\n    options.ConnectionString = \"localhost:6379\";\n    options.DefaultTtl = TimeSpan.FromHours(1);\n    options.CacheKeyPrefix = \"myapp:\";\n    \n    // Retry Configuration\n    options.RetryConfiguration = new RetryConfiguration\n    {\n        MaxAttempts = 3,\n        Strategy = BackoffStrategy.ExponentialWithJitter,\n        InitialDelay = TimeSpan.FromSeconds(1),\n        MaxDelay = TimeSpan.FromSeconds(30),\n        JitterFactor = 0.2 // 20% jitter\n    };\n    \n    // Circuit Breaker\n    options.CircuitBreaker = new CircuitBreakerSettings\n    {\n        Enabled = true,\n        FailureThreshold = 5,\n        BreakDuration = TimeSpan.FromSeconds(30),\n        SuccessThreshold = 2\n    };\n    \n    // Health Monitoring\n    options.HealthMonitoring = new HealthMonitoringSettings\n    {\n        Enabled = true,\n        CheckInterval = TimeSpan.FromSeconds(30),\n        AutoReconnect = true,\n        ConsecutiveFailuresThreshold = 3\n    };\n    \n    // Connection Timeouts\n    options.TimeoutSettings = new ConnectionTimeoutSettings\n    {\n        ConnectTimeout = TimeSpan.FromSeconds(5),\n        SyncTimeout = TimeSpan.FromSeconds(5),\n        AsyncTimeout = TimeSpan.FromSeconds(5),\n        KeepAlive = TimeSpan.FromSeconds(60)\n    };\n});\n```\n\n## 📚 Basic Usage Examples\n\n### Simple Caching with ValueTask (Performance Optimized)\n\n```csharp\npublic class ProductService\n{\n    private readonly IRedisCacheService _cache;\n    \n    public ProductService(IRedisCacheService cache)\n    {\n        _cache = cache;\n    }\n    \n    public async ValueTask\u003cProduct?\u003e GetProductAsync(int productId)\n    {\n        var cacheKey = $\"product:{productId}\";\n        \n        // Try to get from cache - ValueTask for hot path optimization\n        var cached = await _cache.GetAsync\u003cProduct\u003e(cacheKey);\n        if (cached != null)\n            return cached;\n        \n        // Load from database\n        var product = await LoadFromDatabaseAsync(productId);\n        \n        // Cache for 1 hour - ValueTask for reduced allocations\n        if (product != null)\n        {\n            await _cache.SetAsync(cacheKey, product, TimeSpan.FromHours(1));\n        }\n        \n        return product;\n    }\n    \n    public async Task InvalidateProductAsync(int productId)\n    {\n        await _cache.DeleteAsync($\"product:{productId}\");\n    }\n}\n```\n\n### High-Performance Batch Operations\n\n```csharp\npublic class CartService\n{\n    private readonly IRedisCacheService _cache;\n    \n    public CartService(IRedisCacheService cache)\n    {\n        _cache = cache;\n    }\n    \n    // ExecuteBatchAsync - Multiple operations in single round-trip\n    public async Task\u003cCartSummary\u003e GetCartSummaryAsync(string userId)\n    {\n        var result = await _cache.ExecuteBatchAsync(batch =\u003e\n        {\n            batch.GetAsync\u003cCart\u003e($\"cart:{userId}\");\n            batch.GetAsync\u003cUserPreferences\u003e($\"prefs:{userId}\");\n            batch.GetAsync\u003cdecimal\u003e($\"discount:{userId}\");\n            batch.ExistsAsync($\"premium:{userId}\");\n        });\n        \n        return new CartSummary\n        {\n            Cart = result.GetResult\u003cCart\u003e(0),\n            Preferences = result.GetResult\u003cUserPreferences\u003e(1),\n            DiscountRate = result.GetResult\u003cdecimal\u003e(2) ?? 0,\n            IsPremium = result.GetResult\u003cbool\u003e(3)\n        };\n    }\n}\n```\n\n### Basic Pub/Sub\n\n```csharp\npublic class NotificationService\n{\n    private readonly IRedisPubSubService _pubSub;\n    private readonly ILogger\u003cNotificationService\u003e _logger;\n    \n    public NotificationService(IRedisPubSubService pubSub, ILogger\u003cNotificationService\u003e logger)\n    {\n        _pubSub = pubSub;\n        _logger = logger;\n    }\n    \n    // Publisher\n    public async Task SendNotificationAsync(string userId, string message)\n    {\n        var notification = new UserNotification\n        {\n            UserId = userId,\n            Message = message,\n            Timestamp = DateTime.UtcNow\n        };\n        \n        await _pubSub.PublishAsync($\"notifications:{userId}\", notification);\n    }\n    \n    // Subscriber\n    public async Task StartListeningAsync(string userId)\n    {\n        await _pubSub.SubscribeAsync\u003cUserNotification\u003e(\n            $\"notifications:{userId}\",\n            async (notification, ct) =\u003e\n            {\n                _logger.LogInformation(\"Received notification for user {UserId}: {Message}\",\n                    notification.UserId, notification.Message);\n                \n                // Process notification\n                await ProcessNotificationAsync(notification);\n            });\n    }\n}\n\npublic class UserNotification\n{\n    public string UserId { get; set; } = string.Empty;\n    public string Message { get; set; } = string.Empty;\n    public DateTime Timestamp { get; set; }\n}\n```\n\n### Batch Operations\n\n```csharp\npublic class BulkOperationService\n{\n    private readonly IRedisCacheService _cache;\n    \n    public BulkOperationService(IRedisCacheService cache)\n    {\n        _cache = cache;\n    }\n    \n    public async Task\u003cDictionary\u003cint, Product?\u003e\u003e GetProductsAsync(int[] productIds)\n    {\n        // Generate cache keys\n        var keys = productIds.Select(id =\u003e $\"product:{id}\");\n        \n        // Get all products in one operation\n        var cached = await _cache.GetManyAsync\u003cProduct\u003e(keys);\n        \n        var result = new Dictionary\u003cint, Product?\u003e();\n        var missingIds = new List\u003cint\u003e();\n        \n        // Check what we found in cache\n        foreach (var productId in productIds)\n        {\n            var key = $\"product:{productId}\";\n            if (cached.TryGetValue(key, out var product) \u0026\u0026 product != null)\n            {\n                result[productId] = product;\n            }\n            else\n            {\n                missingIds.Add(productId);\n            }\n        }\n        \n        // Load missing from database\n        if (missingIds.Any())\n        {\n            var products = await LoadProductsFromDatabaseAsync(missingIds);\n            \n            // Cache them\n            var toCache = new Dictionary\u003cstring, Product\u003e();\n            foreach (var product in products)\n            {\n                result[product.Id] = product;\n                toCache[$\"product:{product.Id}\"] = product;\n            }\n            \n            await _cache.SetManyAsync(toCache, TimeSpan.FromHours(1));\n        }\n        \n        return result;\n    }\n}\n```\n\n## 🚀 Advanced Usage Examples\n\n### Pattern-Based Subscriptions\n\n```csharp\npublic class GameEventService\n{\n    private readonly IRedisPubSubService _pubSub;\n    private readonly ILogger\u003cGameEventService\u003e _logger;\n    private SubscriptionToken? _token;\n    \n    public GameEventService(IRedisPubSubService pubSub, ILogger\u003cGameEventService\u003e logger)\n    {\n        _pubSub = pubSub;\n        _logger = logger;\n    }\n    \n    public async Task StartMonitoringAsync()\n    {\n        // Subscribe to all game events using pattern\n        _token = await _pubSub.SubscribePatternAsync\u003cGameEvent\u003e(\n            \"game:*:events\",\n            async (gameEvent, ct) =\u003e\n            {\n                _logger.LogInformation(\"Game {GameId} - Event: {EventType}\", \n                    gameEvent.GameId, gameEvent.EventType);\n                \n                switch (gameEvent.EventType)\n                {\n                    case \"player_joined\":\n                        await HandlePlayerJoinedAsync(gameEvent);\n                        break;\n                    case \"game_started\":\n                        await HandleGameStartedAsync(gameEvent);\n                        break;\n                    case \"game_ended\":\n                        await HandleGameEndedAsync(gameEvent);\n                        break;\n                }\n            });\n            \n        // You can also subscribe with channel metadata\n        await _pubSub.SubscribePatternWithChannelAsync\u003cGameEvent\u003e(\n            \"game:*:critical\",\n            async (gameEvent, channel, ct) =\u003e\n            {\n                // Extract game ID from channel name\n                var parts = channel.Split(':');\n                var gameId = parts[1];\n                \n                _logger.LogCritical(\"Critical event in game {GameId}: {Message}\", \n                    gameId, gameEvent.Message);\n                    \n                await SendAlertAsync(gameId, gameEvent);\n            });\n    }\n    \n    public async Task StopMonitoringAsync()\n    {\n        if (_token != null)\n        {\n            await _token.UnsubscribeAsync();\n        }\n    }\n}\n```\n\n### Redis Streams with Consumer Groups\n\n```csharp\npublic class OrderProcessingService : BackgroundService\n{\n    private readonly IRedisStreamService _streams;\n    private readonly ILogger\u003cOrderProcessingService\u003e _logger;\n    \n    public OrderProcessingService(IRedisStreamService streams, ILogger\u003cOrderProcessingService\u003e logger)\n    {\n        _streams = streams;\n        _logger = logger;\n    }\n    \n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        // Create consumer group\n        await _streams.CreateConsumerGroupAsync(\"orders\", \"order-processors\");\n        \n        while (!stoppingToken.IsCancellationRequested)\n        {\n            try\n            {\n                // Read messages from stream\n                var messages = await _streams.ReadGroupAsync\u003cOrder\u003e(\n                    \"orders\",\n                    \"order-processors\",\n                    \"processor-1\",\n                    count: 10,\n                    cancellationToken: stoppingToken);\n                \n                foreach (var message in messages)\n                {\n                    try\n                    {\n                        await ProcessOrderAsync(message.Data);\n                        \n                        // Acknowledge message\n                        await _streams.AcknowledgeAsync(\"orders\", \"order-processors\", message.Id);\n                    }\n                    catch (Exception ex)\n                    {\n                        _logger.LogError(ex, \"Failed to process order {OrderId}\", message.Data?.OrderId);\n                        // Message will be retried\n                    }\n                }\n                \n                // Process pending messages (retry failed ones)\n                var retryResult = await _streams.RetryPendingMessagesAsync\u003cOrder\u003e(\n                    \"orders\",\n                    \"order-processors\",\n                    \"processor-1\",\n                    async (order) =\u003e\n                    {\n                        await ProcessOrderAsync(order);\n                        return true; // Success\n                    },\n                    cancellationToken: stoppingToken);\n                \n                if (retryResult.FailureCount \u003e 0)\n                {\n                    _logger.LogWarning(\"Failed to process {Count} orders, moved to DLQ\", \n                        retryResult.DeadLetterCount);\n                }\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error in order processing loop\");\n                await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);\n            }\n        }\n    }\n    \n    private async Task ProcessOrderAsync(Order? order)\n    {\n        if (order == null) return;\n        \n        _logger.LogInformation(\"Processing order {OrderId}\", order.OrderId);\n        \n        // Process the order\n        await Task.Delay(100); // Simulate work\n        \n        // Publish completion event\n        await _pubSub.PublishAsync($\"orders:{order.OrderId}:completed\", new OrderCompleted\n        {\n            OrderId = order.OrderId,\n            CompletedAt = DateTime.UtcNow\n        });\n    }\n}\n```\n\n### Cache-Aside Pattern with Statistics\n\n```csharp\npublic class CachedRepository\u003cT\u003e where T : class, IEntity\n{\n    private readonly IRedisCacheService _cache;\n    private readonly ILogger\u003cCachedRepository\u003cT\u003e\u003e _logger;\n    private readonly string _entityName;\n    private long _hits = 0;\n    private long _misses = 0;\n    \n    public CachedRepository(IRedisCacheService cache, ILogger\u003cCachedRepository\u003cT\u003e\u003e logger)\n    {\n        _cache = cache;\n        _logger = logger;\n        _entityName = typeof(T).Name.ToLower();\n    }\n    \n    public async Task\u003cT?\u003e GetByIdAsync(string id, Func\u003cTask\u003cT?\u003e\u003e dataLoader)\n    {\n        var key = $\"{_entityName}:{id}\";\n        \n        // Try cache first\n        var cached = await _cache.GetAsync\u003cT\u003e(key);\n        if (cached != null)\n        {\n            Interlocked.Increment(ref _hits);\n            return cached;\n        }\n        \n        Interlocked.Increment(ref _misses);\n        \n        // Load from source\n        var entity = await dataLoader();\n        if (entity != null)\n        {\n            // Cache with sliding expiration\n            await _cache.SetAsync(key, entity, TimeSpan.FromMinutes(15));\n        }\n        \n        return entity;\n    }\n    \n    public async Task\u003cT\u003e GetOrCreateAsync(string id, Func\u003cTask\u003cT\u003e\u003e factory)\n    {\n        var key = $\"{_entityName}:{id}\";\n        \n        var cached = await _cache.GetAsync\u003cT\u003e(key);\n        if (cached != null)\n        {\n            Interlocked.Increment(ref _hits);\n            return cached;\n        }\n        \n        Interlocked.Increment(ref _misses);\n        \n        // Use distributed lock to prevent cache stampede\n        var lockKey = $\"lock:{key}\";\n        var lockAcquired = await _cache.SetAsync(\n            lockKey, \n            \"locked\", \n            TimeSpan.FromSeconds(30), \n            when: When.NotExists);\n        \n        if (lockAcquired)\n        {\n            try\n            {\n                // Double-check after acquiring lock\n                cached = await _cache.GetAsync\u003cT\u003e(key);\n                if (cached != null)\n                    return cached;\n                \n                // Create new entity\n                var entity = await factory();\n                await _cache.SetAsync(key, entity, TimeSpan.FromMinutes(15));\n                return entity;\n            }\n            finally\n            {\n                await _cache.DeleteAsync(lockKey);\n            }\n        }\n        else\n        {\n            // Wait for other thread to populate cache\n            await Task.Delay(100);\n            return await GetByIdAsync(id, factory) ?? await factory();\n        }\n    }\n    \n    public CacheStatistics GetStatistics()\n    {\n        var total = _hits + _misses;\n        return new CacheStatistics\n        {\n            Hits = _hits,\n            Misses = _misses,\n            HitRate = total \u003e 0 ? (double)_hits / total : 0\n        };\n    }\n}\n\npublic interface IEntity\n{\n    string Id { get; }\n}\n\npublic class CacheStatistics\n{\n    public long Hits { get; set; }\n    public long Misses { get; set; }\n    public double HitRate { get; set; }\n}\n```\n\n## 🎨 Custom Serializer Implementation\n\n### Creating a Custom Serializer\n\n```csharp\nusing RedisKit.Serialization;\nusing ProtoBuf;\n\n// Custom Protobuf serializer\npublic class ProtobufRedisSerializer : IRedisSerializer\n{\n    public string Name =\u003e \"Protobuf\";\n    \n    public Task\u003cbyte[]\u003e SerializeAsync\u003cT\u003e(T value, CancellationToken cancellationToken = default) \n        where T : class\n    {\n        if (value == null)\n            return Task.FromResult(Array.Empty\u003cbyte\u003e());\n        \n        using var stream = new MemoryStream();\n        Serializer.Serialize(stream, value);\n        return Task.FromResult(stream.ToArray());\n    }\n    \n    public Task\u003cT?\u003e DeserializeAsync\u003cT\u003e(byte[] data, CancellationToken cancellationToken = default) \n        where T : class\n    {\n        if (data == null || data.Length == 0)\n            return Task.FromResult\u003cT?\u003e(null);\n        \n        using var stream = new MemoryStream(data);\n        var result = Serializer.Deserialize\u003cT\u003e(stream);\n        return Task.FromResult\u003cT?\u003e(result);\n    }\n    \n    public Task\u003cobject?\u003e DeserializeAsync(byte[] data, Type type, CancellationToken cancellationToken = default)\n    {\n        if (data == null || data.Length == 0)\n            return Task.FromResult\u003cobject?\u003e(null);\n        \n        using var stream = new MemoryStream(data);\n        var result = Serializer.Deserialize(type, stream);\n        return Task.FromResult(result);\n    }\n}\n\n// Register custom serializer\npublic class CustomSerializerFactory : IRedisSerializerFactory\n{\n    public IRedisSerializer Create(SerializerType type)\n    {\n        return type switch\n        {\n            SerializerType.Custom =\u003e new ProtobufRedisSerializer(),\n            _ =\u003e RedisSerializerFactory.Create(type)\n        };\n    }\n}\n```\n\n### Using Custom Serializer\n\n```csharp\n// Option 1: Register globally\nservices.AddRedisServices(options =\u003e\n{\n    options.ConnectionString = \"localhost:6379\";\n    options.Serializer = SerializerType.Custom;\n    options.CustomSerializerFactory = new CustomSerializerFactory();\n});\n\n// Option 2: Use for specific service\nservices.AddSingleton\u003cIRedisSerializer, ProtobufRedisSerializer\u003e();\nservices.AddSingleton\u003cIRedisCacheService\u003e(provider =\u003e\n{\n    var database = provider.GetRequiredService\u003cIDatabase\u003e();\n    var logger = provider.GetRequiredService\u003cILogger\u003cRedisCacheService\u003e\u003e();\n    var options = provider.GetRequiredService\u003cIOptions\u003cRedisOptions\u003e\u003e();\n    var serializer = provider.GetRequiredService\u003cProtobufRedisSerializer\u003e();\n    \n    return new RedisCacheService(database, logger, options.Value, serializer);\n});\n```\n\n### Compression Serializer Wrapper\n\n```csharp\nusing System.IO.Compression;\n\npublic class CompressedSerializer : IRedisSerializer\n{\n    private readonly IRedisSerializer _innerSerializer;\n    private readonly CompressionLevel _compressionLevel;\n    \n    public CompressedSerializer(\n        IRedisSerializer innerSerializer, \n        CompressionLevel compressionLevel = CompressionLevel.Optimal)\n    {\n        _innerSerializer = innerSerializer;\n        _compressionLevel = compressionLevel;\n    }\n    \n    public string Name =\u003e $\"Compressed_{_innerSerializer.Name}\";\n    \n    public async Task\u003cbyte[]\u003e SerializeAsync\u003cT\u003e(T value, CancellationToken cancellationToken = default) \n        where T : class\n    {\n        var data = await _innerSerializer.SerializeAsync(value, cancellationToken);\n        \n        using var output = new MemoryStream();\n        using (var gzip = new GZipStream(output, _compressionLevel))\n        {\n            await gzip.WriteAsync(data, 0, data.Length, cancellationToken);\n        }\n        \n        return output.ToArray();\n    }\n    \n    public async Task\u003cT?\u003e DeserializeAsync\u003cT\u003e(byte[] data, CancellationToken cancellationToken = default) \n        where T : class\n    {\n        using var input = new MemoryStream(data);\n        using var gzip = new GZipStream(input, CompressionMode.Decompress);\n        using var output = new MemoryStream();\n        \n        await gzip.CopyToAsync(output, cancellationToken);\n        var decompressed = output.ToArray();\n        \n        return await _innerSerializer.DeserializeAsync\u003cT\u003e(decompressed, cancellationToken);\n    }\n    \n    public async Task\u003cobject?\u003e DeserializeAsync(\n        byte[] data, \n        Type type, \n        CancellationToken cancellationToken = default)\n    {\n        using var input = new MemoryStream(data);\n        using var gzip = new GZipStream(input, CompressionMode.Decompress);\n        using var output = new MemoryStream();\n        \n        await gzip.CopyToAsync(output, cancellationToken);\n        var decompressed = output.ToArray();\n        \n        return await _innerSerializer.DeserializeAsync(decompressed, type, cancellationToken);\n    }\n}\n\n// Usage\nservices.AddSingleton\u003cIRedisSerializer\u003e(provider =\u003e\n{\n    var innerSerializer = RedisSerializerFactory.Create(SerializerType.MessagePack);\n    return new CompressedSerializer(innerSerializer, CompressionLevel.Fastest);\n});\n```\n\n## 🏗️ Dependency Injection\n\n```csharp\n// In your Program.cs or Startup.cs:\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add Redis services with configuration from appsettings.json\nbuilder.Services.Configure\u003cRedisOptions\u003e(\n    builder.Configuration.GetSection(\"Redis\"));\n\nbuilder.Services.AddRedisServices(options =\u003e\n{\n    builder.Configuration.GetSection(\"Redis\").Bind(options);\n});\n\n// In your services:\npublic class UserService\n{\n    private readonly IRedisCacheService _cache;\n    private readonly IRedisPubSubService _pubSub;\n    private readonly IRedisStreamService _stream;\n    private readonly ILogger\u003cUserService\u003e _logger;\n\n    public UserService(\n        IRedisCacheService cache, \n        IRedisPubSubService pubSub,\n        IRedisStreamService stream,\n        ILogger\u003cUserService\u003e logger)\n    {\n        _cache = cache;\n        _pubSub = pubSub;\n        _stream = stream;\n        _logger = logger;\n    }\n\n    public async Task\u003cUser?\u003e GetUserAsync(string userId)\n    {\n        // Try cache first\n        var cached = await _cache.GetAsync\u003cUser\u003e($\"user:{userId}\");\n        if (cached != null)\n            return cached;\n\n        // Load from database\n        var user = await LoadFromDatabaseAsync(userId);\n        \n        // Cache for future requests\n        if (user != null)\n        {\n            await _cache.SetAsync($\"user:{userId}\", user, TimeSpan.FromHours(1));\n            \n            // Publish update event\n            await _pubSub.PublishAsync(\"user-updates\", new UserLoadedEvent \n            { \n                UserId = userId \n            });\n        }\n\n        return user;\n    }\n}\n```\n\n## 🎯 Backoff Strategies\n\nRedisKit supports multiple backoff strategies for retry operations:\n\n- **Fixed**: Constant delay between retries\n- **Linear**: Linear increase in delay\n- **Exponential**: Exponential increase in delay\n- **ExponentialWithJitter**: Exponential with random jitter to prevent thundering herd\n- **DecorrelatedJitter**: AWS-recommended strategy with decorrelated jitter\n\n```csharp\noptions.RetryConfiguration = new RetryConfiguration\n{\n    Strategy = BackoffStrategy.DecorrelatedJitter,\n    MaxAttempts = 5,\n    InitialDelay = TimeSpan.FromMilliseconds(100),\n    MaxDelay = TimeSpan.FromSeconds(10)\n};\n```\n\n## 🚀 Performance Tips \u0026 Best Practices\n\n### 1. **Connection Management**\n\n```csharp\n// ❌ DON'T: Create new connections for each operation\npublic async Task BadExample()\n{\n    var connection = await ConnectionMultiplexer.ConnectAsync(\"localhost\");\n    var db = connection.GetDatabase();\n    await db.StringSetAsync(\"key\", \"value\");\n    connection.Dispose(); // Connection closed!\n}\n\n// ✅ DO: Use dependency injection and connection pooling\npublic class GoodExample\n{\n    private readonly IRedisCacheService _cache; // Injected, pooled connection\n    \n    public async Task SetValueAsync()\n    {\n        await _cache.SetAsync(\"key\", \"value\");\n    }\n}\n```\n\n### 2. **Batch Operations for Better Performance**\n\n```csharp\n// ❌ DON'T: Multiple round trips\npublic async Task SlowApproach(string[] userIds)\n{\n    var users = new List\u003cUser\u003e();\n    foreach (var id in userIds)\n    {\n        var user = await _cache.GetAsync\u003cUser\u003e($\"user:{id}\");\n        if (user != null) users.Add(user);\n    }\n}\n\n// ✅ DO: Single batch operation\npublic async Task FastApproach(string[] userIds)\n{\n    var keys = userIds.Select(id =\u003e $\"user:{id}\");\n    var results = await _cache.GetManyAsync\u003cUser\u003e(keys);\n    var users = results.Values.Where(u =\u003e u != null).ToList();\n}\n```\n\n### 3. **Big Key Handling**\n\n```csharp\n// ❌ DON'T: Store huge objects as single keys\npublic async Task BadBigKey()\n{\n    var hugeList = new List\u003cItem\u003e(1_000_000); // 1 million items!\n    await _cache.SetAsync(\"huge:list\", hugeList); // This blocks Redis!\n}\n\n// ✅ DO: Split large datasets\npublic async Task GoodBigKeyHandling()\n{\n    var items = GetLargeDataset();\n    var chunks = items.Chunk(1000); // Split into chunks of 1000\n    \n    var tasks = chunks.Select((chunk, index) =\u003e \n        _cache.SetAsync($\"items:chunk:{index}\", chunk.ToList(), TimeSpan.FromHours(1))\n    );\n    \n    await Task.WhenAll(tasks);\n}\n\n// ✅ DO: Use Redis Streams for large datasets\npublic async Task StreamApproach(List\u003cItem\u003e items)\n{\n    foreach (var batch in items.Chunk(100))\n    {\n        foreach (var item in batch)\n        {\n            await _streamService.AddAsync(\"items:stream\", item);\n        }\n    }\n}\n```\n\n### 4. **Pipeline Usage**\n\n```csharp\n// ❌ DON'T: Sequential operations\npublic async Task SlowSequential()\n{\n    await _cache.SetAsync(\"key1\", \"value1\");\n    await _cache.SetAsync(\"key2\", \"value2\");\n    await _cache.SetAsync(\"key3\", \"value3\");\n    // 3 round trips to Redis\n}\n\n// ✅ DO: Use batch/pipeline operations\npublic async Task FastPipeline()\n{\n    var items = new Dictionary\u003cstring, string\u003e\n    {\n        [\"key1\"] = \"value1\",\n        [\"key2\"] = \"value2\",\n        [\"key3\"] = \"value3\"\n    };\n    \n    await _cache.SetManyAsync(items, TimeSpan.FromHours(1));\n    // Single round trip!\n}\n```\n\n### 5. **Memory Optimization**\n\n```csharp\n// ✅ Use appropriate serializers\nservices.AddRedisServices(options =\u003e\n{\n    // MessagePack: Fastest and smallest\n    options.Serializer = SerializerType.MessagePack;\n    \n    // JSON: Human readable, larger size\n    // options.Serializer = SerializerType.SystemTextJson;\n});\n\n// ✅ Compress large objects\npublic class CompressedCacheService\n{\n    private readonly IRedisCacheService _cache;\n    \n    public async Task SetCompressedAsync\u003cT\u003e(string key, T value) where T : class\n    {\n        if (value is string str \u0026\u0026 str.Length \u003e 1000)\n        {\n            // Compress strings larger than 1KB\n            var compressed = Compress(str);\n            await _cache.SetAsync($\"{key}:compressed\", compressed);\n        }\n        else\n        {\n            await _cache.SetAsync(key, value);\n        }\n    }\n}\n```\n\n### 6. **Key Expiration Strategies**\n\n```csharp\n// ✅ Use sliding expiration for frequently accessed data\npublic async Task\u003cT?\u003e GetWithSlidingExpirationAsync\u003cT\u003e(string key) where T : class\n{\n    var value = await _cache.GetAsync\u003cT\u003e(key);\n    if (value != null)\n    {\n        // Reset expiration on each access\n        await _cache.ExpireAsync(key, TimeSpan.FromMinutes(30));\n    }\n    return value;\n}\n\n// ✅ Use absolute expiration for time-sensitive data\npublic async Task SetDailyReportAsync(Report report)\n{\n    var tomorrow = DateTime.UtcNow.Date.AddDays(1);\n    var ttl = tomorrow - DateTime.UtcNow;\n    \n    await _cache.SetAsync($\"report:{DateTime.UtcNow:yyyy-MM-dd}\", report, ttl);\n}\n```\n\n### 7. **Avoid Hot Keys**\n\n```csharp\n// ❌ DON'T: Single key for global counter\npublic async Task IncrementGlobalCounter()\n{\n    var count = await _cache.GetAsync\u003cint\u003e(\"global:counter\");\n    await _cache.SetAsync(\"global:counter\", count + 1);\n    // This key becomes a bottleneck!\n}\n\n// ✅ DO: Distribute load across multiple keys\npublic async Task IncrementDistributedCounter()\n{\n    var shard = Random.Shared.Next(0, 10); // 10 shards\n    var key = $\"counter:shard:{shard}\";\n    \n    var count = await _cache.GetAsync\u003cint\u003e(key);\n    await _cache.SetAsync(key, count + 1);\n}\n\npublic async Task\u003cint\u003e GetTotalCount()\n{\n    var tasks = Enumerable.Range(0, 10)\n        .Select(i =\u003e _cache.GetAsync\u003cint\u003e($\"counter:shard:{i}\"));\n    \n    var counts = await Task.WhenAll(tasks);\n    return counts.Sum();\n}\n```\n\n### 8. **Circuit Breaker for Resilience**\n\n```csharp\n// ✅ Configure circuit breaker to prevent cascade failures\nservices.AddRedisServices(options =\u003e\n{\n    options.CircuitBreaker = new CircuitBreakerSettings\n    {\n        Enabled = true,\n        FailureThreshold = 5,        // Open after 5 failures\n        BreakDuration = TimeSpan.FromSeconds(30),  // Stay open for 30s\n        SuccessThreshold = 2          // Need 2 successes to close\n    };\n});\n```\n\n### 9. **Monitoring \u0026 Metrics**\n\n```csharp\n// ✅ Track cache hit rates\npublic class MetricsCacheService\n{\n    private readonly IRedisCacheService _cache;\n    private readonly IMetrics _metrics;\n    \n    public async Task\u003cT?\u003e GetWithMetricsAsync\u003cT\u003e(string key) where T : class\n    {\n        var value = await _cache.GetAsync\u003cT\u003e(key);\n        \n        if (value != null)\n            _metrics.Increment(\"cache.hits\");\n        else\n            _metrics.Increment(\"cache.misses\");\n            \n        return value;\n    }\n}\n```\n\n### 10. **Pub/Sub Performance**\n\n```csharp\n// ✅ Use pattern subscriptions wisely\npublic class EfficientPubSub\n{\n    private readonly IRedisPubSubService _pubSub;\n    \n    public async Task SubscribeEfficiently()\n    {\n        // Instead of subscribing to many individual channels\n        // Use pattern subscription\n        await _pubSub.SubscribePatternAsync\u003cEvent\u003e(\n            \"events:*\",  // Single pattern subscription\n            async (evt, ct) =\u003e await ProcessEventAsync(evt, ct)\n        );\n    }\n}\n```\n\n## ⚡ Recent Performance Improvements\n\n### SetManyAsync Optimization with Lua Scripts\n\nWe've implemented a significant performance optimization for batch operations using Lua scripts:\n\n- **90-95% performance improvement** for batch SET operations\n- **Single round-trip** to Redis instead of O(n) operations\n- **Automatic fallback** for environments without Lua support\n- **Parallel serialization** for optimal CPU utilization\n\n#### Before vs After Performance\n\n| Batch Size | Before (ms) | After (ms) | Improvement    |\n|------------|-------------|------------|----------------|\n| 100 items  | 52          | 3          | **94% faster** |\n| 500 items  | 258         | 14         | **95% faster** |\n| 1000 items | 516         | 28         | **95% faster** |\n| 5000 items | 2,580       | 140        | **95% faster** |\n\n*Benchmarks on local Redis with 1KB objects*\n\n## 📊 Performance Benchmarks\n\n### Serializer Performance Comparison\n\n| Method                          | JSON (ns) | MessagePack (ns) | Speed Improvement | Memory Improvement   |\n|---------------------------------|-----------|------------------|-------------------|----------------------|\n| **Small Object Serialize**      | 331.8     | 143.2            | **2.3x faster**   | **5.6x less memory** |\n| **Large Object Serialize**      | 3,569.1   | 1,940.7          | **1.8x faster**   | **Similar memory**   |\n| **Array Serialize (100 items)** | 28,143.8  | 11,556.8         | **2.4x faster**   | **3.2x less memory** |\n| **Small Object Deserialize**    | 628.0     | 256.5            | **2.4x faster**   | **2.1x less memory** |\n| **Async Serialize**             | 355.9     | 173.8            | **2.0x faster**   | **2.8x less memory** |\n| **Async Deserialize**           | 823.8     | 290.0            | **2.8x faster**   | **2.0x less memory** |\n\n\u003e **Recommendation**: Use MessagePack for production workloads requiring high performance and low memory usage.\n\n### Redis Operations Performance\n\n| Operation  | Single Item | Batch (100 items) | Batch (1000 items) |\n|------------|-------------|-------------------|--------------------|\n| Set        | ~1ms        | ~5ms              | ~40ms              |\n| Get        | ~0.8ms      | ~4ms              | ~35ms              |\n| Pub/Sub    | ~0.5ms      | N/A               | N/A                |\n| Stream Add | ~1.2ms      | ~8ms              | ~70ms              |\n\n*Benchmarks on local Redis, actual performance depends on network latency and Redis server specs*\n\n## 📊 Performance Considerations\n\n- **Connection Pooling**: Connections are automatically pooled and reused\n- **Pipelining**: Commands are automatically pipelined for better throughput\n- **Memory Efficiency**: Uses ArrayPool and MemoryPool to minimize allocations\n- **Concurrent Operations**: Thread-safe operations with minimal locking\n- **Circuit Breaker**: Prevents cascading failures in distributed systems\n- **Automatic Cleanup**: Inactive handlers are automatically cleaned up to prevent memory leaks\n\n## 🧪 Testing\n\nThe library includes comprehensive unit tests with 100% coverage of critical paths:\n\n```bash\ndotnet test\n\n# Results\nPassed!  - Failed: 0, Passed: 140, Skipped: 12, Total: 152\n```\n\n## 📋 Requirements\n\n- .NET 9.0 or higher\n- Redis Server 5.0 or higher (6.0+ recommended for Streams support)\n\n## 🔒 Security \u0026 Dependencies\n\n- **Automated Dependency Updates**: Dependabot configured for weekly security updates\n- **Security Policy**: See [SECURITY.md](SECURITY.md) for vulnerability reporting\n- **CI/CD Pipeline**: Automated testing on multiple platforms (Windows, Linux, macOS)\n- **Auto-merge**: Minor and patch updates are automatically merged after passing tests\n\n## 🤝 Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## 📚 Documentation\n\nFor more detailed documentation, please visit our [Wiki](https://github.com/ersintarhan/RedisKit/wiki).\n\n## 🆕 Redis 7.x Usage Examples\n\n### Redis Functions\n\n```csharp\n// Create and load a function library\nvar functionService = app.Services.GetRequiredService\u003cIRedisFunction\u003e();\n\n// Build a function library with FunctionLibraryBuilder\nvar library = new FunctionLibraryBuilder()\n    .WithName(\"mylib\")\n    .WithEngine(\"LUA\")\n    .WithDescription(\"My custom functions\")\n    .AddFunction(\"greet\", @\"\n        function(keys, args)\n            return 'Hello, ' .. args[1]\n        end\n    \")\n    .AddFunction(\"sum\", @\"\n        function(keys, args)\n            return tonumber(args[1]) + tonumber(args[2])\n        end\n    \")\n    .Build();\n\n// Load the library\nawait functionService.LoadAsync(library);\n\n// Call functions\nvar greeting = await functionService.CallAsync\u003cstring\u003e(\"greet\", args: new[] { \"World\" });\n// Result: \"Hello, World\"\n\nvar sum = await functionService.CallAsync\u003clong\u003e(\"sum\", args: new[] { \"10\", \"20\" });\n// Result: 30\n\n// Call with array return type\nvar results = await functionService.CallAsync\u003cstring[]\u003e(\"get_users\");\n// Result: [\"user1\", \"user2\", \"user3\"]\n```\n\n### Sharded Pub/Sub\n\n```csharp\n// Sharded Pub/Sub for better scalability in cluster mode\nvar shardedPubSub = app.Services.GetRequiredService\u003cIRedisShardedPubSub\u003e();\n\n// Subscribe to a sharded channel (distributed across shards)\nvar token = await shardedPubSub.SubscribeAsync\u003cOrderMessage\u003e(\n    \"orders:new\",\n    async (message, ct) =\u003e\n    {\n        Console.WriteLine($\"Order received on shard: {message.ShardId}\");\n        await ProcessOrder(message.Data);\n    });\n\n// Publish to sharded channel (automatically routed to correct shard)\nvar subscribers = await shardedPubSub.PublishAsync(\n    \"orders:new\", \n    new OrderMessage { OrderId = 123, Amount = 99.99m });\n\n// Note: Pattern subscriptions are NOT supported in sharded pub/sub\n// Use regular pub/sub for patterns\n```\n\n## 🐛 Known Issues\n\n- Stream service tests are currently skipped as they require a real Redis instance\n- PUBSUB NUMSUB command returns local handler count only (StackExchange.Redis limitation)\n- Sharded Pub/Sub does not support pattern subscriptions (Redis limitation)\n\n## 🚦 Roadmap\n\n- [x] Redis Functions support (Redis 7.x) - ✅ Completed\n- [x] Sharded Pub/Sub (Redis 7.x) - ✅ Completed\n- [x] Distributed locking primitives - ✅ Completed\n- [x] Redis Sentinel support (High Availability with automatic failover)\n- [ ] Redis Cluster native support\n- [ ] ACL v2 improvements (Redis 7.x)\n- [ ] Client-side caching support\n- [ ] Geo-spatial operations\n- [ ] Time-series data support\n- [ ] OpenTelemetry integration\n- [ ] Prometheus metrics export","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fersintarhan%2Frediskit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fersintarhan%2Frediskit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fersintarhan%2Frediskit/lists"}