### Example 'out' file start Source: https://seredis.dev/RespLogging.html Shows the typical start of an 'out' file captured by the logging tunnel, beginning with the RESP bulk string length and command tokens for the client handshake. ```text $6 CLIENT $7 SETNAME ... ``` -------------------------------- ### Example 'in' file start Source: https://seredis.dev/RespLogging.html Shows the typical start of an 'in' file captured by the logging tunnel, containing simple string responses from the server. ```text +OK +OK +OK ... ``` -------------------------------- ### C# example: Using IServer.HotKeys* methods to profile hot keys Source: https://seredis.dev/HotKeys.html Shows how to start, fetch, and reset a HOTKEYS profiling session on a Redis 8.6+ server. The example uses HotKeysStartAsync with a duration, HotKeysGetAsync to retrieve results, and HotKeysResetAsync to discard data. Note that only one capture can be active at a time, and results can be fetched multiple times. ```csharp // Get the server instance. IConnectionMultiplexer muxer = ... // connect to Redis 8.6 or later var server = muxer.GetServer(endpoint); // or muxer.GetServer(key) // Start the capture; you can specify a duration, or manually use the HotKeysStop[Async] method; specifying // a duration is recommended, so that the profiler will not be left running in the case of failure. // Optional parameters allow you to specify the metrics to capture, the sample ratio, and the key slots to include; // by default, all metrics are captured, every command is sampled, and all key slots are included. await server.HotKeysStartAsync(duration: TimeSpan.FromSeconds(30)); // Now either do some work ourselves, or await for some other activity to happen: await Task.Delay(TimeSpan.FromSeconds(35)); // whatever happens: happens // Fetch the results; note that this does not stop the capture, and you can fetch the results multiple times // either while it is running, or after it has completed - but only a single capture can be active at a time. var result = await server.HotKeysGetAsync(); // ...investigate the results... // Optional: discard the active capture data at the server, if any. await server.HotKeysResetAsync(); ``` -------------------------------- ### Use IServer to list keys and flush database Source: https://seredis.dev/KeysScan.html Shows how to start from a server (not a database) to run server-scoped commands. Gets the target server, lists keys matching a pattern, and flushes the database. The Keys method uses SCAN when available, otherwise KEYS, and has no Async counterpart. ```csharp // get the target server var server = conn.GetServer(someServer); // show all keys in database 0 that include "foo" in their name foreach(var key in server.Keys(pattern: "*foo*")) { Console.WriteLine(key); } // completely wipe ALL keys from database 0 server.FlushDatabase(); ``` -------------------------------- ### Basic ExecuteResp usage with GET command Source: https://seredis.dev/Execute.html Sends a GET command using ExecuteResp with a single key argument wrapped as RedisKey. The command name and arguments are passed in command-expected order. Keys participate in cluster slot routing and key prefixing. The example allocates an array on each call; see the low-allocation form for hot paths. ```csharp using ConnectionMultiplexer conn = /* init code */; var db = conn.GetDatabase(); // note: see "Leasing the argument buffer" below using RespResult result = db.ExecuteResp("GET", new RedisKeyOrValue[] { (RedisKey)"mykey" }); // ... ``` -------------------------------- ### Example TestConfig.json override Source: https://seredis.dev/Testing.html Shows an example JSON configuration for overriding test server settings. Only specified properties are overridden; unspecified ones use defaults. If a server isn't specified, related tests are skipped as inconclusive. ```json { "RunLongRunning": true, "PrimaryServer": "192.168.0.42", "PrimaryPort": 12345 } ``` -------------------------------- ### Connect to primary/replica Redis setup with ConnectionMultiplexer.Connect Source: https://seredis.dev/Basics.html Connects to a primary/replica setup by specifying all nodes in the logical Redis tier. The multiplexer automatically identifies the primary. If both nodes are primaries, a tie-breaker key can optionally be specified, though this condition is rare. ```csharp ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("server1:6379,server2:6379"); ``` -------------------------------- ### ToyProfiler with per-thread sessions Source: https://seredis.dev/Profiling_v2.html Alternative to the shared session example: each thread gets its own ProfilingSession, and results are collected per thread. After running, perThreadTimings contains 16 entries of 1,000 IProfilingCommand objects keyed by Thread. ```csharp ConnectionMultiplexer conn = /* initialization */; var profiler = new ToyProfiler(); conn.RegisterProfiler(() => profiler.PerThreadSession); var threads = new List(); var perThreadTimings = new ConcurrentDictionary>(); for (var i = 0; i < 16; i++) { var db = conn.GetDatabase(i); var thread = new Thread( delegate() { var threadTasks = new List(); profiler.PerThreadSession = new ProfilingSession(); for (var j = 0; j < 1000; j++) { var task = db.StringSetAsync("" + j, "" + j); threadTasks.Add(task); } Task.WaitAll(threadTasks.ToArray()); perThreadTimings[Thread.CurrentThread] = profiler.PerThreadSession.FinishProfiling().ToList(); } ); threads.Add(thread); } threads.ForEach(thread => thread.Start()); threads.ForEach(thread => thread.Join()); ``` -------------------------------- ### Create Health Check from Existing Instance with Builder(policy) Source: https://seredis.dev/Failover.html Use the Builder(policy) overload to start from an existing health check instance, for example to adjust a policy that was handed to you. ```csharp // take the group's configured check and probe it harder for one member HealthCheck stricter = new HealthCheck.Builder(conn.Options.HealthCheck) { ProbeCount = 9 }; ``` -------------------------------- ### Async StringSet and StringGet with await Source: https://seredis.dev/Basics.html Demonstrates asynchronous usage with the Async suffix and the await keyword. The example sets a string value and then retrieves it, printing the result. ```csharp string value = "abcdefg"; await db.StringSetAsync("mykey", value); ... string value = await db.StringGetAsync("mykey"); Console.WriteLine(value); // writes: "abcdefg" ``` -------------------------------- ### Log output for refused maintenance notifications Source: https://seredis.dev/ServerMaintenanceEvent.html Example log output when the server declines the opt-in, including the reason given. ```text 10.0.0.1:6379: Maintenance notifications refused (ERR maintenance notifications are disabled on this server) ``` -------------------------------- ### HotKeysStartAsync Source: https://seredis.dev/HotKeys.html Starts a server-side profiling session to capture CPU and network usage by key. This method is available on the `IServer` interface and is used to begin capturing hot keys data. ```APIDOC ## HotKeysStartAsync ### Description Starts a server-side profiling session to capture CPU and network usage by key. This method is available on the `IServer` interface and is used to begin capturing hot keys data. ### Method Async method on `IServer` ### Endpoint `IServer.HotKeysStartAsync` ### Parameters - **duration** (TimeSpan) - Required - The duration for which the profiling should run. It is recommended to specify a duration to avoid leaving the profiler running in case of failure. - **metrics** (optional) - Optional - The metrics to capture. By default, all metrics are captured. - **sampleRatio** (optional) - Optional - The sample ratio for profiling. By default, every command is sampled. - **keySlots** (optional) - Optional - The key slots to include. By default, all key slots are included. ### Request Example ```csharp await server.HotKeysStartAsync(duration: TimeSpan.FromSeconds(30)); ``` ### Response No return value (Task). ``` -------------------------------- ### Log output for accepted maintenance notifications Source: https://seredis.dev/ServerMaintenanceEvent.html Example log output when the server accepts the maintenance notification opt-in. ```text 10.0.0.1:6379: Requesting maintenance notifications (Auto) 10.0.0.1:6379: Maintenance notifications accepted ``` -------------------------------- ### Log output for maintenance handoff Source: https://seredis.dev/ServerMaintenanceEvent.html Example log output for a maintenance handoff, which replaces connections. ```text 10.0.0.1:6379: Maintenance handoff: Recycle -> 10.0.0.2:6379: db.example.com now resolves to 10.0.0.2:6379 ``` -------------------------------- ### Get a database instance Source: https://seredis.dev/Basics.html Retrieves the default database from the Redis connection. The returned IDatabase is a cheap pass-thru object and does not need to be stored. ```csharp IDatabase db = redis.GetDatabase(); ``` -------------------------------- ### Retrieve array metadata with ArrayInfoAsync Source: https://seredis.dev/Arrays.html Gets array metadata. The full parameter requests additional details. The example prints Count, Length, and NextInsertIndex properties. ```csharp ArrayInfo info = await db.ArrayInfoAsync(key); ArrayInfo fullInfo = await db.ArrayInfoAsync(key, full: true); Console.WriteLine($"Count: {info.Count}"); Console.WriteLine($"Length: {info.Length}"); Console.WriteLine($"Next insert index: {info.NextInsertIndex}"); ``` -------------------------------- ### Set up keyspace isolation with WithKeyPrefix Source: https://seredis.dev/KeyspaceNotifications.html Sets up a keyspace-isolated database with a tenant-specific prefix and writes an order key. The prefix is used later to filter and strip keys in key notifications. ```csharp // multi-tenant scenario using keyspace isolation byte[] keyPrefix = Encoding.UTF8.GetBytes("client1234:"); var db = conn.GetDatabase().WithKeyPrefix(keyPrefix); // we will later commit order data for example: await db.StringSetAsync("order/123", "ISBN 9789123684434"); ``` -------------------------------- ### Convert between configuration string and ConfigurationOptions Source: https://seredis.dev/Configuration.html Shows how to convert between a configuration string and a ConfigurationOptions instance. Parse converts a string to options, and ToString converts options back to a string. ```csharp ConfigurationOptions options = ConfigurationOptions.Parse(configString); ``` ```csharp string configString = options.ToString(); ``` -------------------------------- ### Read scalar from RespResult with ScriptEvaluateResp Source: https://seredis.dev/Scripting.html Use ScriptEvaluateResp to get a RespResult and read a scalar value. The example shows three ways to read: CopyTo into a pre-sized buffer, ReadLease for a poolable handle (may share the reply's buffer), and ReadRedisValue for a RedisValue. Dispose the RespResult and any leases promptly; ReadLease may share the reply's buffer, keeping it alive until both are disposed. ```csharp using RespResult result = db.ScriptEvaluateResp("return redis.call('get', KEYS[1])", new RedisKey[] { "mykey" }, default); if (!result.IsNull) { var reader = result.ReadScalar(); // cheapest: copy straight into a buffer you already own, sized exactly via ScalarLength() byte[] buffer = new byte[reader.ScalarLength()]; int written = reader.CopyTo(buffer); // or, as an owned, poolable handle on the value - note this usually shares the reply's buffer // rather than copying, so dispose it promptly (see below): using Lease? lease = reader.ReadLease(); // or, if you just want the usual RedisValue/string: RedisValue value = reader.ReadRedisValue(); } ``` -------------------------------- ### Get and Set VectorSet JSON Attributes Source: https://seredis.dev/VectorSets.html Gets and sets JSON attributes for a member. The set operation uses a raw string literal for the JSON payload. ```csharp // Get attributes var json = await db.VectorSetGetAttributesJsonAsync(key, "product-123"); // Set attributes await db.VectorSetSetAttributesJsonAsync( key, "product-123", """{"category":"electronics","updated":"2024-01-15"}""" ); ``` -------------------------------- ### Example replay output with an invalid RESP response Source: https://seredis.dev/RespLogging.html Example output after deliberately introducing an error into the captured file. The -ERR message is normal, but the final ECHO pair shows an invalid response that causes an exception. ```text > CLUSTER NODES < -ERR This instance has cluster support disabled > GET __Booksleeve_TieBreak < (null) > ECHO ... < -Invalid bulk string terminator Unhandled exception. StackExchange.Redis.RedisConnectionException: Invalid bulk string terminator ``` -------------------------------- ### Connect with username and password Source: https://seredis.dev/Authentication.html Specify the `user` and `password` tokens in the connection string. If no `user` is provided, the `default` user is assumed. An authentication-token can be used in place of a classic password in some cases. ```csharp var muxer = await ConnectionMultiplexer.ConnectAsync("myserver,ssl=true,user=myuser,password=mypassword"); ``` -------------------------------- ### Apply runtime options to Redis configuration Source: https://seredis.dev/Configuration.html Stores basic configuration as a string and applies runtime-specific details via ConfigurationOptions before connecting. Sets ClientName and AllowAdmin after parsing. ```csharp string configString = GetRedisConfiguration(); var options = ConfigurationOptions.Parse(configString); options.ClientName = GetAppName(); // only known at runtime options.AllowAdmin = true; conn = ConnectionMultiplexer.Connect(options); ``` -------------------------------- ### Pipeline two GETs with StringGetAsync and db.Wait Source: https://seredis.dev/PipelinesMultiplexers.html Pipelines two asynchronous GET operations using StackExchange.Redis. The db.Wait method applies the configured synchronous timeout, but you can use aPending.Wait() or Task.WaitAll(aPending, bPending) instead. This reduces latency and packet fragmentation. ```csharp var aPending = db.StringGetAsync("a"); var bPending = db.StringGetAsync("b"); var a = db.Wait(aPending); var b = db.Wait(bPending); ``` -------------------------------- ### Using the Availability sub-namespace Source: https://seredis.dev/Failover.html Import the StackExchange.Redis and StackExchange.Redis.Availability namespaces to access the availability features. ```csharp using StackExchange.Redis; using StackExchange.Redis.Availability; ``` -------------------------------- ### Subscribe to all tenants with KeySpacePattern Source: https://seredis.dev/KeyspaceNotifications.html Creates a channel pattern to observe keyspace notifications for all tenants matching the pattern. Use with similar parsing code that manually extracts the client from the full key. ```csharp var channel = RedisChannel.KeySpacePattern("client*:order/*", db.Database); ``` -------------------------------- ### Get Members by Lexicographical Range with VectorSetRangeAsync Source: https://seredis.dev/VectorSets.html Retrieves members in lexicographical order. The first call gets all members; the second limits to a range with a count; the third excludes boundaries using Exclude.Both. Results are returned as disposable objects; access via Span. ```csharp // Get all members using var allMembers = await db.VectorSetRangeAsync(key); // ... access allMembers.Span, etc // Get members in a specific range using var rangeMembers = await db.VectorSetRangeAsync( key, start: "product-100", end: "product-200", count: 50 ); // ... access rangeMembers.Span, etc // Exclude boundaries using var members = await db.VectorSetRangeAsync( key, start: "product-100", end: "product-200", exclude: Exclude.Both ); // ... access members.Span, etc ``` -------------------------------- ### VectorSetRandomMembersAsync Source: https://seredis.dev/VectorSets.html Gets multiple random members from the vector set. ```APIDOC ## VectorSetRandomMembersAsync ### Description Gets multiple random members from the vector set. ### Method Async method ### Endpoint `db.VectorSetRandomMembersAsync(key, count)` ### Parameters - **key** (string) - Required - The key of the vector set. - **count** (int) - Required - The number of random members to retrieve. ### Returns - `IEnumerable` - A collection of random members. ### Example ```csharp var members = await db.VectorSetRandomMembersAsync(key, count: 5); ``` ``` -------------------------------- ### VectorSetRandomMemberAsync Source: https://seredis.dev/VectorSets.html Gets a single random member from the vector set. ```APIDOC ## VectorSetRandomMemberAsync ### Description Gets a single random member from the vector set. ### Method Async method ### Endpoint `db.VectorSetRandomMemberAsync(key)` ### Parameters - **key** (string) - Required - The key of the vector set. ### Returns - `string?` - A random member, or `null` if the set is empty. ### Example ```csharp var member = await db.VectorSetRandomMemberAsync(key); ``` ``` -------------------------------- ### Run tests on Windows with build.cmd Source: https://seredis.dev/Testing.html Command to run the tests as part of the build on Windows. Execute from the repository root. ```powershell .\build.cmd -BuildNumber local ``` -------------------------------- ### Subscribe to tenant-specific keyspace notifications with TryParse Source: https://seredis.dev/KeyspaceNotifications.html Subscribes to keyspace notifications for a specific tenant using RedisChannel.KeySpacePrefix and parses them with KeyNotification.TryParse, which filters and strips the prefix. Includes a commented contrast showing the non-recommended manual approach with KeyStartsWith. ```csharp var sub = conn.GetSubscriber(); // subscribe to the specific tenant as a prefix: var channel = RedisChannel.KeySpacePrefix("client1234:order/", db.Database); sub.SubscribeAsync(channel, (recvChannel, recvValue) => { // by including prefix in the TryParse, we filter out notifications that are not for this client // *and* the key is sliced internally to remove this prefix when reading if (KeyNotification.TryParse(keyPrefix, recvChannel, recvValue, out var notification)) { // if we get here, the key prefix was a match var key = notification.GetKey(); // "order/123" - note no prefix // ... } /* // for contrast only: this is *not* usually the recommended approach when using keyspace isolation if (KeyNotification.TryParse(recvChannel, recvValue, out var notification) && notification.KeyStartsWith(keyPrefix)) { var key = notification.GetKey(); // "client1234:order/123" - note prefix is included // ... } */ }); ``` -------------------------------- ### ArrayNextAsync - Get Next Write Head Source: https://seredis.dev/Arrays.html Returns the next write head position without inserting. ```APIDOC ## ArrayNextAsync ### Description Returns the next write head position without inserting. ### Method ArrayNextAsync ### Parameters - **key** (string) - The key of the array. ### Request Example ```csharp RedisArrayIndex? next = await db.ArrayNextAsync(key); ``` ### Response Returns a nullable `RedisArrayIndex` representing the next write head position. ``` -------------------------------- ### Zero-downtime migration: add new datacenter, shift traffic, remove old Source: https://seredis.dev/Failover.html Performs a zero-downtime migration by adding a new datacenter with a high weight, waiting for it to connect, reducing the old member's weight to shift traffic, and then removing the old member. Uses Task.Delay to allow connection and traffic shift, and checks IsConnected before proceeding. ```csharp var group = (IConnectionGroup)conn; // Add the new datacenter var newDC = new ConnectionGroupMember("new-location.redis.example.com:6379", name: "New Location") { Weight = 10 // High weight to prefer the new location }; await group.AddAsync(newDC); // Wait for the new member to be fully connected and healthy await Task.Delay(TimeSpan.FromSeconds(5)); if (newDC.IsConnected) { Console.WriteLine("New datacenter is online and healthy"); // Reduce weight of old datacenter var oldDC = members[0]; oldDC.Weight = 1; // Wait for traffic to shift await Task.Delay(TimeSpan.FromSeconds(10)); // Remove the old datacenter if (group.Remove(oldDC)) { Console.WriteLine("Old datacenter removed successfully"); } } ``` -------------------------------- ### Use ConfigurationOptions with ConnectGroupAsync Source: https://seredis.dev/Failover.html Creates two ConfigurationOptions objects with endpoints, passwords, and SSL enabled, then builds a ConnectionGroupMember array and connects via ConnectGroupAsync. ```csharp var eastConfig = new ConfigurationOptions { EndPoints = { "us-east-1.redis.example.com:6379", "us-east-2.redis.example.com:6379" }, Password = "your-password", Ssl = true, }; var westConfig = new ConfigurationOptions { EndPoints = { "us-west-1.redis.example.com:6379", "us-west-2.redis.example.com:6379" }, Password = "another-different-password", Ssl = true, }; ConnectionGroupMember[] members = [ new(eastConfig, name: "US East"), new(westConfig, name: "US West") ]; await using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members); ``` -------------------------------- ### Connect to Redis with configuration Source: https://seredis.dev/Configuration.html Connects to Redis using a configuration that can be a ConfigurationOptions instance or a string. The ECHO command is used to verify the connection, so the ACL user must have permission to run it. ```csharp var conn = ConnectionMultiplexer.Connect(configuration); ``` -------------------------------- ### Customize Health Check with Builder Source: https://seredis.dev/Failover.html Customize a health check by setting only the properties that differ from the defaults. A new Builder starts from the defaults. ```csharp HealthCheck customHealthCheck = new HealthCheck.Builder { ProbeCount = 5 }; // everything else defaulted MultiGroupOptions options = new MultiGroupOptions.Builder { HealthCheck = customHealthCheck, HealthCheckInterval = TimeSpan.FromSeconds(15), }; ``` -------------------------------- ### Replace StringGet + KeyDelete transaction with StringGetDeleteAsync Source: https://seredis.dev/rules/SER303.html Shows the flagged transaction pattern (StringGetAsync + KeyDeleteAsync) and the suggested single command StringGetDeleteAsync (GETDEL) that performs both atomically. The transaction exists only to make two commands atomic; a single command already does both. ```csharp // flagged var tran = db.CreateTransaction(); var value = tran.StringGetAsync(key); _ = tran.KeyDeleteAsync(key); await tran.ExecuteAsync(); // suggested RedisValue value = await db.StringGetDeleteAsync(key); ``` -------------------------------- ### AsyncLocalProfiler class and registration Source: https://seredis.dev/Profiling_v2.html Provides a per-async-context ProfilingSession automatically. Register with multiplexer.RegisterProfiler(profiler.GetSession). Call FinishProfiling() to get command timings. ```csharp class AsyncLocalProfiler { private readonly AsyncLocal perThreadSession = new AsyncLocal(); public ProfilingSession GetSession() { var val = perThreadSession.Value; if (val == null) { perThreadSession.Value = val = new ProfilingSession(); } return val; } } ... ``` ```csharp var profiler = new AsyncLocalProfiler(); multiplexer.RegisterProfiler(profiler.GetSession); ``` -------------------------------- ### Use RedisArrayIndex for large unsigned indexes Source: https://seredis.dev/Arrays.html Demonstrates using RedisArrayIndex with implicit conversions from int, long, and ulong. This allows normal small indexes to be used directly while still supporting the full unsigned index range. ```csharp await db.ArraySetAsync(key, 42, "answer"); await db.ArraySetAsync(key, new RedisArrayIndex(10_000_000UL), "large index"); ``` -------------------------------- ### Read ranges forward and backward with ArrayGetRangeAsync Source: https://seredis.dev/Arrays.html Reads a range of values. Ranges can be read forward or backward by ordering the start and end indexes. ```csharp RedisValue[] forward = await db.ArrayGetRangeAsync(key, 0, 5); RedisValue[] reverse = await db.ArrayGetRangeAsync(key, 5, 0); ``` -------------------------------- ### Set contiguous range with ArraySetAsync Source: https://seredis.dev/Arrays.html Writes a contiguous range of values starting at index 0. Returns the number of cells newly filled. ```csharp int inserted = await db.ArraySetAsync(key, 0, ["a", "b", "c"]); ``` -------------------------------- ### Connect to multiple Redis servers with options Source: https://seredis.dev/Configuration.html Connects to multiple Redis servers with comma-delimited options. Ports are specified with a colon and options use an equals sign. ```csharp var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,allowAdmin=true"); ``` -------------------------------- ### Get Approximate Vector for Member Source: https://seredis.dev/VectorSets.html Retrieves the approximate vector for a member. The returned lease must be disposed; check for null before accessing the span. ```csharp using var vectorLease = await db.VectorSetGetApproximateVectorAsync(key, "product-123"); if (vectorLease != null) { ReadOnlySpan vector = vectorLease.Value.Span; // Use the vector data } ``` -------------------------------- ### Suppress SER004 locally in C# Source: https://seredis.dev/exp/SER004.html Use this in C# source to suppress the SER004 diagnostic locally, for example around specific code sections. ```csharp #pragma warning disable SER004 ```