title: Conditional execution

summary: Understand how `NRedisStack` uses conditional execution

title: `When` conditions

text:
Several commands have variants that only execute if the key they change
already exists (or alternatively, if it doesn't already exist). For
example, the [`SET`]() command has the
variants [`SETEX`]() (set when the key exists),
and [`SETNX`]() (set when the key doesn't exist).

Instead of providing the different variants of these commands, `NRedisStack`
lets you add a `When` condition to the basic command to access its variants.
The following example demonstrates this for the
[`HashSet()`]() command.

<!-- < clients-example pipe_trans_tutorial when_condition "C#" >}}
< /clients-example >}} -->

[code example]

The available conditions are `When.Exists`, `When.NotExists`, and the default
`When.Always`.

title: Transaction conditions

text:
`NRedisStack` also supports a more extensive set of conditions that you
can add to transactions. They are implemented internally using
[`WATCH`]() commands in a way that is
guaranteed to be safe, without interactions between different clients.
Although conditions don't provide exactly the same behavior as
explicit `WATCH` commands, they are convenient to use and execute
efficiently.

The example below shows how to use the `AddCondition()` method on
a transaction to let it run only if a specified hash key does not
already exist. See
[Pipelines and transactions]()
for more information about transactions.

<!--< clients-example pipe_trans_tutorial trans_watch "C#" >}}
< /clients-example >}} -->

[code example]

The table below describes the full set of conditions you can add to
a transaction. Note that you can add more than one condition to the
same transaction if necessary.

| Condition | Description |
| :-- | :-- |
| `HashEqual` | Enforces that the given hash-field must have the specified value. |
| `HashExists` | Enforces that the given hash-field must exist. |
| `HashNotEqual` | Enforces that the given hash-field must not have the specified value. |
| `HashNotExists` | Enforces that the given hash-field must not exist. |
| `KeyExists` | Enforces that the given key must exist. |
| `KeyNotExists` | Enforces that the given key must not exist. |
| `ListIndexEqual` | Enforces that the given list index must have the specified value. |
| `ListIndexExists` | Enforces that the given list index must exist. |
| `ListIndexNotEqual` | Enforces that the given list index must not have the specified value. |
| `ListIndexNotExists` | Enforces that the given list index must not exist. |
| `StringEqual` | Enforces that the given key must have the specified value. |
| `StringNotEqual` | Enforces that the given key must not have the specified value. |
| `HashLengthEqual` | Enforces that the given hash length is a certain value. |
| `HashLengthLessThan` | Enforces that the given hash length is less than a certain value. |
| `HashLengthGreaterThan` | Enforces that the given hash length is greater than a certain value. |
| `StringLengthEqual` | Enforces that the given string length is a certain value. |
| `StringLengthLessThan` | Enforces that the given string length is less than a certain value. |
| `StringLengthGreaterThan` | Enforces that the given string length is greater than a certain value. |
| `ListLengthEqual` | Enforces that the given list length is a certain value. |
| `ListLengthLessThan` | Enforces that the given list length is less than a certain value. |
| `ListLengthGreaterThan` | Enforces that the given list length is greater than a certain value. |
| `SetLengthEqual` | Enforces that the given set cardinality is a certain value. |
| `SetLengthLessThan` | Enforces that the given set cardinality is less than a certain value. |
| `SetLengthGreaterThan` | Enforces that the given set cardinality is greater than a certain value. |
| `SetContains` | Enforces that the given set contains a certain member. |
| `SetNotContains` | Enforces that the given set does not contain a certain member. |
| `SortedSetLengthEqual` | Enforces that the given sorted set cardinality is a certain value. |
| `SortedSetLengthEqual` | Enforces that the given sorted set contains a certain number of members with scores in the given range. |
| `SortedSetLengthLessThan` | Enforces that the given sorted set cardinality is less than a certain value. |
| `SortedSetLengthLessThan` | Enforces that the given sorted set contains less than a certain number of members with scores in the given range. |
| `SortedSetLengthGreaterThan` | Enforces that the given sorted set cardinality is greater than a certain value. |
| `SortedSetLengthGreaterThan` | Enforces that the given sorted set contains more than a certain number of members with scores in the given range. |
| `SortedSetContains` | Enforces that the given sorted set contains a certain member. |
| `SortedSetNotContains` | Enforces that the given sorted set does not contain a certain member. |
| `SortedSetEqual` | Enforces that the given sorted set member must have the specified score. |
| `SortedSetNotEqual` | Enforces that the given sorted set member must not have the specified score. |
| `SortedSetScoreExists` | Enforces that the given sorted set must have the given score. |
| `SortedSetScoreNotExists` | Enforces that the given sorted set must not have the given score. |
| `SortedSetScoreExists` | Enforces that the given sorted set must have the specified count of the given score. |
| `SortedSetScoreNotExists` | Enforces that the given sorted set must not have the specified count of the given score. |
| `StreamLengthEqual` | Enforces that the given stream length is a certain value. |
| `StreamLengthLessThan` | Enforces that the given stream length is less than a certain value. |
| `StreamLengthGreaterThan` | Enforces that the given stream length is greater than a certain value. |

code:
bool resp7 = db.HashSet("Details", "SerialNumber", "12345");
Console.WriteLine(resp7); // >>> true

db.HashSet("Details", "SerialNumber", "12345A", When.NotExists);
string resp8 = db.HashGet("Details", "SerialNumber");
Console.WriteLine(resp8); // >>> 12345

db.HashSet("Details", "SerialNumber", "12345A");
string resp9 = db.HashGet("Details", "SerialNumber");
Console.WriteLine(resp9); // >>> 12345A

section_id: when-conditions

code:
var watchedTrans = new Transaction(db);

watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));

watchedTrans.Db.HashSetAsync(
    "customer:39182",
    new HashEntry[]{
        new HashEntry("name", "David"),
        new HashEntry("age", "27")
    }
);

bool succeeded = watchedTrans.Execute();
Console.WriteLine(succeeded); // >>> true

section_id: transaction-conditions


title: Connect to the server

summary: Connect your .NET application to a Redis database

page_type: content

title: Basic connection

role: overview

text:
You can connect to the server simply by passing a string of the
form "hostname:port" to the `Connect()` method (for example,
"localhost:6379"). However, you can also connect using a
`ConfigurationOptions` parameter. Use this to specify a
username, password, and many other options:

[code example]

title: Connect to a Redis cluster

role: content

text:
The basic connection will use the
[Cluster API]()
if it is available without any special configuration. However, if you know
the addresses and ports of several cluster nodes, you can specify them all
during connection in the `Endpoints` parameter:

[code example]

title: Connect to your production Redis with TLS

text:
When you deploy your application, use TLS and follow the [Redis security]() guidelines.

Before connecting your application to the TLS-enabled Redis server, ensure that your certificates and private keys are in the correct format.

To convert user certificate and private key from the PEM format to `pfx`, use this command:

[code example]

Enter password to protect your `pfx` file.

Establish a secure connection with your Redis database using this snippet.

[code example]

title: Multiplexing

text:
Although example code typically works with a single connection,
real-world code often uses multiple connections at the same time.
Opening and closing connections repeatedly is inefficient, so it is best
to manage open connections carefully to avoid this.

Several other
Redis client libraries use *connection pools* to reuse a set of open
connections efficiently. NRedisStack uses a different approach called
*multiplexing*, which sends all client commands and responses over a
single connection. NRedisStack manages multiplexing for you automatically.
This gives high performance without requiring any extra coding.
See
[Connection pools and multiplexing]()
for more information.

code:
using NRedisStack;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

ConfigurationOptions conf = new ConfigurationOptions {
    EndPoints = { "localhost:6379" },
    User = "yourUsername",
    Password = "yourPassword"
};

ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(conf);
IDatabase db = redis.GetDatabase();

db.StringSet("foo", "bar");
Console.WriteLine(db.StringGet("foo")); // prints bar

section_id: basic-connection

code:
ConfigurationOptions options = new ConfigurationOptions
{
    //list of available nodes of the cluster along with the endpoint port.
    EndPoints = {
        { "localhost", 16379 },
        { "localhost", 16380 },
        // ...
    },
};

ConnectionMultiplexer cluster = ConnectionMultiplexer.Connect(options);
IDatabase db = cluster.GetDatabase();

db.StringSet("foo", "bar");
Console.WriteLine(db.StringGet("foo")); // prints bar

section_id: connect-to-a-redis-cluster

code: openssl pkcs12 -inkey redis_user_private.key -in redis_user.crt -export -out redis.pfx

code:
ConfigurationOptions options = new ConfigurationOptions
{
    EndPoints = { { "my-redis.cloud.redislabs.com", 6379 } },
    User = "default",  // use your Redis user. More info https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/
    Password = "secret", // use your Redis password
    Ssl = true,
    SslProtocols = System.Security.Authentication.SslProtocols.Tls12
};

options.CertificateSelection += delegate
{
    return X509CertificateLoader.LoadPkcs12FromFile("redis.pfx", "secret"); // use the password you specified for pfx file
};
options.CertificateValidation += ValidateServerCertificate;

bool ValidateServerCertificate(
        object sender,
        X509Certificate? certificate,
        X509Chain? chain,
        SslPolicyErrors sslPolicyErrors)
{
    if (certificate == null) {
        return false;
    }

    var ca = X509CertificateLoader.LoadCertificateFromFile("redis_ca.pem");
    bool verdict = (certificate.Issuer == ca.Subject);
    if (verdict) {
        return true;
    }
    Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
    return false;
}

ConnectionMultiplexer muxer = ConnectionMultiplexer.Connect(options);

//Creation of the connection to the DB
IDatabase conn = muxer.GetDatabase();

//send SET command
conn.StringSet("foo", "bar");

//send GET command and print the value
Console.WriteLine(conn.StringGet("foo"));


title: Error handling

summary: Learn how to handle errors when using NRedisStack.

title: Overview

text:
NRedisStack uses **exceptions** to signal errors. Code examples in the documentation often omit error handling for brevity, but it is essential in production code. This page explains how NRedisStack's error handling works and how to apply common error handling patterns.

For an overview of error types and handling strategies, see [Error handling]().
See also [Production usage]()
for more information on connection management, timeouts, and other aspects of
app reliability.

title: Exception types

role: errors

text:
NRedisStack throws exceptions to signal errors. Common exception types include:

| Exception | When it occurs | Recoverable | Recommended action |
|---|---|---|---|
| `RedisConnectionException` | Connection refused or lost | ✅ | Retry with backoff or fall back |
| `RedisTimeoutException` | Operation exceeded timeout | ✅ | Retry with backoff |
| `RedisCommandException` | Invalid command or arguments | ❌ | Fix the command or arguments |
| `RedisServerException` | Invalid operation on server | ❌ | Fix the operation or data |

See [Categories of errors]()
for a more detailed discussion of these errors and their causes.

title: Applying error handling patterns

text:
The [Error handling]() overview
describes four main patterns. The sections below show how to implement them in
NRedisStack:

title: Pattern 1: Fail fast

text:
Catch specific exceptions that represent unrecoverable errors and re-throw them (see
[Pattern 1: Fail fast]()
for a full description):

[code example]

title: Pattern 2: Graceful degradation

text:
Catch specific errors and fall back to an alternative, where possible (see
[Pattern 2: Graceful degradation]()
for a full description):

[code example]

title: Pattern 3: Retry with backoff

text:
Retry on temporary errors such as timeouts (see
[Pattern 3: Retry with backoff]()
for a full description):

[code example]

See also [Timeouts]()
for more information on configuring timeouts in NRedisStack.

title: Pattern 4: Log and continue

text:
Log non-critical errors and continue (see
[Pattern 4: Log and continue]()
for a full description):

[code example]

title: Async error handling

text:
Error handling works the usual way with `async`/`await`, as shown in the
example below:

[code example]

title: See also

role: related

text:
- [Error handling]()
- [Production usage]()

code:
using NRedisStack;
using StackExchange.Redis;

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

try {
    var result = db.StringGet("key");
} catch (RedisCommandException) {
    // This indicates a bug in the code, such as using
    // StringGet on a list key.
    throw;
}

code:
try {
    var cachedValue = db.StringGet(key);
    return cachedValue.ToString();
} catch (RedisConnectionException) {
    logger.LogWarning("Cache unavailable, using database");

    // Fallback to database
    return database.Get(key);
}

code:
const int maxRetries = 3;
int retryDelay = 100;

for (int attempt = 0; attempt < maxRetries; attempt++) {
    try {
        return db.StringGet(key).ToString();
    } catch (RedisTimeoutException) {
        if (attempt < maxRetries - 1) {
            Thread.Sleep(retryDelay);
            retryDelay *= 2;  // Exponential backoff
        } else {
            throw;
        }
    }
}

code:
try {
    db.StringSet(key, value, TimeSpan.FromSeconds(3600));
} catch (RedisConnectionException) {
    logger.LogWarning($"Failed to cache {key}, continuing without cache");
    // Application continues normally
}

code:
using NRedisStack;
using StackExchange.Redis;

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

async Task<string> GetWithFallbackAsync(string key) {
    try {
        var result = await db.StringGetAsync(key);
        if (result.HasValue) {
            return result.ToString();
        }
    } catch (RedisConnectionException) {
        logger.LogWarning("Cache unavailable");
        return await database.GetAsync(key);
    }

    return await database.GetAsync(key);
}

section_id: async-error-handling


title: NRedisStack guide (C#/.NET)

title: Connect to the server

summary: Learn how to use Redis Search with JSON and hash documents.

title: Index and query documents

summary: Learn how to index and query vector embeddings with Redis

title: Index and query vectors

summary: Index and query embeddings with Redis vector sets

title: Vector set embeddings

summary: Learn how to use approximate calculations with Redis.

title: Probabilistic data types

summary: Learn how to use Redis pipelines and transactions

title: Pipelines and transactions

title: Conditional execution

title: Error handling

summary: Get your NRedisStack app ready for production

title: Production usage

title: Install

role: setup

text:
Using the `dotnet` CLI, run:

[code example]

title: Connect and test

text:
Add the following imports to your source file:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

Connect to localhost on port 6379. The client supports both synchronous and asynchronous commands.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

You can test the connection by storing and retrieving a simple string.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

Store and retrieve a HashMap.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

title: Redis Open Source modules

text:
To access Redis Open Source capabilities, use the appropriate interface like this:

[code example]

title: More information

text: See the other pages in this section for more information and examples.

code: dotnet add package NRedisStack

section_id: install

code:
#include <stdio.h>
#include <stdlib.h>

#include <hiredis/hiredis.h>

int main() {
    // The `redisContext` type represents the connection
    // to the Redis server. Here, we connect to the
    // default host and port.
    redisContext *c = redisConnect("127.0.0.1", 6379);

    // Check if the context is null or if a specific
    // error occurred.
    if (c == NULL || c->err) {
        if (c != NULL) {
            printf("Error: %s\n", c->errstr);
            // handle error
        } else {
            printf("Can't allocate redis context\n");
        }

        exit(1);
    }

    // Set a string key.
    redisReply *reply = redisCommand(c, "SET foo bar");
    printf("Reply: %s\n", reply->str); // >>> Reply: OK
    freeReplyObject(reply);

    // Get the key we have just stored.
    reply = redisCommand(c, "GET foo");
    printf("Reply: %s\n", reply->str); // >>> Reply: bar
    freeReplyObject(reply);

    // Close the connection.
    redisFree(c);
}

section_id: connect-and-test

code:
using NRedisStack;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

code:
import (
	"context"
	"fmt"

	"github.com/redis/go-redis/v9"
)

code:
import redis.clients.jedis.RedisClient;
import java.util.HashMap;
import java.util.Map;

code:
import { createClient } from 'redis';

const client = createClient();

client.on('error', err => console.log('Redis Client Error', err));

await client.connect();

await client.set('key', 'value');
const value = await client.get('key');
console.log(value); // >>> value

await client.hSet('user-session:123', {
    name: 'John',
    surname: 'Smith',
    company: 'Redis',
    age: 29
})

let userSession = await client.hGetAll('user-session:123');
console.log(JSON.stringify(userSession, null, 2));
/* >>>
{
  "surname": "Smith",
  "name": "John",
  "company": "Redis",
  "age": "29"
}
 */

await client.quit();

code:
import { Redis } from 'ioredis';

const redis = new Redis();

await redis.set('key', 'value');
const value = await redis.get('key');
console.log(value); // >>> value

await redis.hset('user-session:123', {
    name: 'John',
    surname: 'Smith',
    company: 'Redis',
    age: 29
});

const userSession = await redis.hgetall('user-session:123');
console.log(JSON.stringify(userSession, null, 2));
/* >>>
{
  "surname": "Smith",
  "name": "John",
  "company": "Redis",
  "age": "29"
}
 */

redis.disconnect();

code:
<?php

require 'vendor/autoload.php';

use Predis\Client as PredisClient;

$r = new PredisClient([
                'scheme'   => 'tcp',
                'host'     => '127.0.0.1',
                'port'     => 6379,
                'password' => '',
                'database' => 0,
            ]);

echo $r->set('foo', 'bar'), PHP_EOL;
// >>> OK

echo $r->get('foo'), PHP_EOL;
// >>> bar

$r->hset('user-session:123', 'name', 'John');
$r->hset('user-session:123', 'surname', 'Smith');
$r->hset('user-session:123', 'company', 'Redis');
$r->hset('user-session:123', 'age', 29);

echo var_export($r->hgetall('user-session:123')), PHP_EOL;
/* >>>
array (
  'name' => 'John',
  'surname' => 'Smith',
  'company' => 'Redis',
  'age' => '29',
)
*/

code:
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

r.set('foo', 'bar')
# True
r.get('foo')
# bar

r.hset('user-session:123', mapping={
    'name': 'John',
    "surname": 'Smith',
    "company": 'Redis',
    "age": 29
})
# True

r.hgetall('user-session:123')
# {'surname': 'Smith', 'name': 'John', 'company': 'Redis', 'age': '29'}

r.close()

code: require 'redis'

code: use redis::AsyncCommands;

code: use redis::Commands;

code:
var muxer = await ConnectionMultiplexer.ConnectAsync("localhost:6379");
        var db = muxer.GetDatabase();

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

code:
rdb := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password
		DB:       0,  // use default DB
		Protocol: 2,
	})

	ctx := context.Background()

code: RedisClient jedis = new RedisClient("redis://localhost:6379");

code: r = Redis.new

code:
let mut r = match redis::Client::open("redis://127.0.0.1") {
            Ok(client) => {
                match client.get_multiplexed_async_connection().await {
                    Ok(conn) => conn,
                    Err(e) => {
                        println!("Failed to connect to Redis: {e}");
                        return;
                    }
                }
            },
            Err(e) => {
                println!("Failed to create Redis client: {e}");
                return;
            }
        };

code:
let mut r = match redis::Client::open("redis://127.0.0.1") {
            Ok(client) => {
                match client.get_connection() {
                    Ok(conn) => conn,
                    Err(e) => {
                        println!("Failed to connect to Redis: {e}");
                        return;
                    }
                }
            },
            Err(e) => {
                println!("Failed to create Redis client: {e}");
                return;
            }
        };

code:
await db.StringSetAsync("foo", "bar");
        string? fooResult = await db.StringGetAsync("foo");
        Console.WriteLine(fooResult); // >>> bar

code:
db.StringSet("foo", "bar");
        Console.WriteLine(db.StringGet("foo")); // >>> bar

code:
err := rdb.Set(ctx, "foo", "bar", 0).Err()
	if err != nil {
		panic(err)
	}

	val, err := rdb.Get(ctx, "foo").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("foo", val) // >>> foo bar

code:
String res1 = jedis.set("bike:1", "Deimos");
        System.out.println(res1); // >>> OK

        String res2 = jedis.get("bike:1");
        System.out.println(res2); // >>> Deimos

code:
if let Ok(res) = r.set("foo", "bar").await {
            let res: String = res;
            println!("{res}"); // >>> OK
        } else {
            println!("Error setting foo");
        }

        match r.get("foo").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> bar
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

code:
if let Ok(res) = r.set("foo", "bar") {
            let res: String = res;
            println!("{res}");    // >>> OK
        } else {
            println!("Error setting foo");
        }

        match r.get("foo") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> bar
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

code:
var hash = new HashEntry[] {
            new HashEntry("name", "John"),
            new HashEntry("surname", "Smith"),
            new HashEntry("company", "Redis"),
            new HashEntry("age", "29"),
            };
        await db.HashSetAsync("user-session:123", hash);

        var hashFields = await db.HashGetAllAsync("user-session:123");
        Console.WriteLine(String.Join("; ", hashFields));
        // >>> name: John; surname: Smith; company: Redis; age: 29

code:
var hash = new HashEntry[] {
            new HashEntry("name", "John"),
            new HashEntry("surname", "Smith"),
            new HashEntry("company", "Redis"),
            new HashEntry("age", "29"),
        };
        db.HashSet("user-session:123", hash);

        var hashFields = db.HashGetAll("user-session:123");
        Console.WriteLine(String.Join("; ", hashFields));
        // >>> name: John; surname: Smith; company: Redis; age: 29

code:
hashFields := []string{
		"model", "Deimos",
		"brand", "Ergonom",
		"type", "Enduro bikes",
		"price", "4972",
	}

	res1, err := rdb.HSet(ctx, "bike:1", hashFields).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res1) // >>> 4

	res2, err := rdb.HGet(ctx, "bike:1", "model").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res2) // >>> Deimos

	res3, err := rdb.HGet(ctx, "bike:1", "price").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res3) // >>> 4972

	res4, err := rdb.HGetAll(ctx, "bike:1").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res4)
	// >>> map[brand:Ergonom model:Deimos price:4972 type:Enduro bikes]

code:
import io.lettuce.core.*;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;

public class ConnectBasicTest {

    public void connectBasic() {
        RedisURI uri = RedisURI.Builder
                .redis("localhost", 6379)
                .build();

        RedisClient client = RedisClient.create(uri);
        StatefulRedisConnection<String, String> connection = client.connect();
        RedisCommands<String, String> commands = connection.sync();

        commands.set("foo", "bar");
        String result = commands.get("foo");
        System.out.println(result); // >>> bar

        connection.close();
        client.shutdown();
    }
}

code:
Map<String, String> hash = new HashMap<>();
        hash.put("name", "John");
        hash.put("surname", "Smith");
        hash.put("company", "Redis");
        hash.put("age", "29");

        Long res3 = jedis.hset("user-session:123", hash);
        System.out.println(res3); // >>> 4

        Map<String, String> res4 = jedis.hgetAll("user-session:123");
        System.out.println(res4);
        // >>> {name=John, surname=Smith, company=Redis, age=29}

code:
require 'redis'

r = Redis.new

r.set 'foo', 'bar'
value = r.get('foo')
puts value # >>> bar

r.hset 'user-session:123', 'name', 'John'
r.hset 'user-session:123', 'surname', 'Smith'
r.hset 'user-session:123', 'company', 'Redis'
r.hset 'user-session:123', 'age', 29

hash_value = r.hgetall('user-session:123')
puts hash_value
# >>> {"name"=>"John", "surname"=>"Smith", "company"=>"Redis", "age"=>"29"}

r.close()

code:
let hash_fields = [
            ("model", "Deimos"),
            ("brand", "Ergonom"),
            ("type", "Enduro bikes"),
            ("price", "4972"),
        ];

        if let Ok(res) = r.hset_multiple("bike:1", &hash_fields).await {
            let res: String = res;
            println!("{res}"); // >>> OK
        } else {
            println!("Error setting bike:1");
        }

        match r.hget("bike:1", "model").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1 model: {e}");
                return;
            }
        }

        match r.hget("bike:1", "price").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> 4972
            },
            Err(e) => {
                println!("Error getting bike:1 price: {e}");
                return;
            }
        }

        match r.hgetall("bike:1").await {
            Ok(res) => {
                let res: Vec<(String, String)> = res;

                for (key, value) in res {
                    println!("{key}: {value}");
                }
                // >>> model: Deimos
                // >>> brand: Ergonom
                // >>> type: Enduro bikes
                // >>> price: 4972
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }

code:
let hash_fields = [
            ("model", "Deimos"),
            ("brand", "Ergonom"),
            ("type", "Enduro bikes"),
            ("price", "4972"),
        ];

        if let Ok(res) = r.hset_multiple("bike:1", &hash_fields) {
            let res: String = res;
            println!("{res}"); // >>> OK
        } else {
            println!("Error setting bike:1");
        }

        match r.hget("bike:1", "model") {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1 model: {e}");
                return;
            }
        }

        match r.hget("bike:1", "price") {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> 4972
            },
            Err(e) => {
                println!("Error getting bike:1 price: {e}");
                return;
            }
        }

        match r.hgetall("bike:1") {
            Ok(res) => {
                let res: Vec<(String, String)> = res;

                for (key, value) in res {
                    println!("{key}: {value}");
                }
                // >>> model: Deimos
                // >>> brand: Ergonom
                // >>> type: Enduro bikes
                // >>> price: 4972
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        }

code:
IBloomCommands bf = db.BF();
ICuckooCommands cf = db.CF();
ICmsCommands cms = db.CMS();
IGraphCommands graph = db.GRAPH();
ITopKCommands topk = db.TOPK();
ITdigestCommands tdigest = db.TDIGEST();
ISearchCommands ft = db.FT();
IJsonCommands json = db.JSON();
ITimeSeriesCommands ts = db.TS();

section_id: redis-open-source-modules


title: Probabilistic data types

title: Set operations

text:
Redis supports the following approximate set operations:

-   [Membership](#set-membership): The
    [Bloom filter]() and
    [Cuckoo filter]()
    data types let you track whether or not a given item is a member of a set.
-   [Cardinality](#set-cardinality): The
    [HyperLogLog]()
    data type gives you an approximate value for the number of items in a set, also
    known as the *cardinality* of the set.

The sections below describe these operations in more detail.

title: Set membership

text:
[Bloom filter]() and
[Cuckoo filter]()
objects provide a set membership operation that lets you track whether or not a
particular item has been added to a set. These two types provide different
trade-offs for memory usage and speed, so you can select the best one for your
use case. Note that for both types, there is an asymmetry between presence and
absence of items in the set. If an item is reported as absent, then it is definitely
absent, but if it is reported as present, then there is a small chance it may really be
absent.

Instead of storing strings directly, like a [set](),
a Bloom filter records the presence or absence of the
[hash value](https://en.wikipedia.org/wiki/Hash_function) of a string.
This gives a very compact representation of the
set's membership with a fixed memory size, regardless of how many items you
add. The following example adds some names to a Bloom filter representing
a list of users and checks for the presence or absence of users in the list.
Note that you must use the `BF()` method to access the Bloom filter commands.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

A Cuckoo filter has similar features to a Bloom filter, but also supports
a deletion operation to remove hashes from a set, as shown in the example
below. Note that you must use the `CF()` method to access the Cuckoo filter
commands.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

Which of these two data types you choose depends on your use case.
Bloom filters are generally faster than Cuckoo filters when adding new items,
and also have better memory usage. Cuckoo filters are generally faster
at checking membership and also support the delete operation. See the
[Bloom filter]() and
[Cuckoo filter]()
reference pages for more information and comparison between the two types.

title: Set cardinality

text:
A [HyperLogLog]()
object calculates the cardinality of a set. As you add
items, the HyperLogLog tracks the number of distinct set members but
doesn't let you retrieve them or query which items have been added.
You can also merge two or more HyperLogLogs to find the cardinality of the
[union](https://en.wikipedia.org/wiki/Union_(set_theory)) of the sets they
represent.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

The main benefit that HyperLogLogs offer is their very low
memory usage. They can count up to 2^64 items with less than
1% standard error using a maximum 12KB of memory. This makes
them very useful for counting things like the total of distinct
IP addresses that access a website or the total of distinct
bank card numbers that make purchases within a day.

title: Statistics

text:
Redis supports several approximate statistical calculations
on numeric data sets:

-   [Frequency](#frequency): The
    [Count-min sketch]()
    data type lets you find the approximate frequency of a labeled item in a data stream.
-   [Quantiles](#quantiles): The
    [t-digest]()
    data type estimates the quantile of a query value in a data stream.
-   [Ranking](#ranking): The
    [Top-K]() data type
    estimates the ranking of labeled items by frequency in a data stream.

The sections below describe these operations in more detail.

title: Frequency

text:
A [Count-min sketch]()
(CMS) object keeps count of a set of related items represented by
string labels. The count is approximate, but you can specify
how close you want to keep the count to the true value (as a fraction)
and the acceptable probability of failing to keep it in this
desired range. For example, you can request that the count should
stay within 0.1% of the true value and have a 0.05% probability
of going outside this limit. The example below shows how to create
a Count-min sketch object, add data to it, and then query it.
Note that you must use the `CMS()` method to access the Count-min
sketch commands.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

The advantage of using a CMS over keeping an exact count with a
[sorted set]()
is that that a CMS has very low and fixed memory usage, even for
large numbers of items. Use CMS objects to keep daily counts of
items sold, accesses to individual web pages on your site, and
other similar statistics.

title: Quantiles

text:
A [quantile](https://en.wikipedia.org/wiki/Quantile) is the value
below which a certain fraction of samples lie. For example, with
a set of measurements of people's heights, the quantile of 0.75 is
the value of height below which 75% of all people's heights lie.
[Percentiles](https://en.wikipedia.org/wiki/Percentile) are equivalent
to quantiles, except that the fraction is expressed as a percentage.

A [t-digest]()
object can estimate quantiles from a set of values added to it
without having to store each value in the set explicitly. This can
save a lot of memory when you have a large number of samples.

The example below shows how to add data samples to a t-digest
object and obtain some basic statistics, such as the minimum and
maximum values, the quantile of 0.75, and the
[cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function)
(CDF), which is effectively the inverse of the quantile function. It also
shows how to merge two or more t-digest objects to query the combined
data set. Note that you must use the `TDIGEST()` method to access the
t-digest commands.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

A t-digest object also supports several other related commands, such
as querying by rank. See the
[t-digest]()
reference for more information.

title: Ranking

text:
A [Top-K]()
object estimates the rankings of different labeled items in a data
stream according to frequency. For example, you could use this to
track the top ten most frequently-accessed pages on a website, or the
top five most popular items sold.

The example below adds several different items to a Top-K object
that tracks the top three items (this is the second parameter to
the `TOPK().Reserve()` method). It also shows how to list the
top *k* items and query whether or not a given item is in the
list. Note that you must use the `TOPK()` method to access the
Top-K commands.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

code:
bool[] res1 = db.BF().MAdd(
            "recorded_users", "andy", "cameron", "david", "michelle"
        );
        Console.WriteLine(string.Join(", ", res1));
        // >>> true, true, true, true

        bool res2 = db.BF().Exists("recorded_users", "cameron");
        Console.WriteLine(res2); // >>> true

        bool res3 = db.BF().Exists("recorded_users", "kaitlyn");
        Console.WriteLine(res3); // >>> false

section_id: set-membership

code:
res1, err := rdb.BFMAdd(
		ctx,
		"recorded_users",
		"andy", "cameron", "david", "michelle",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res1) // >>> [true true true true]

	res2, err := rdb.BFExists(ctx,
		"recorded_users", "cameron",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res2) // >>> true

	res3, err := rdb.BFExists(ctx, "recorded_users", "kaitlyn").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res3) // >>> false

code:
List<Boolean> res1 = jedis.bfMAdd(
            "recorded_users",
            "andy", "cameron", "david", "michelle"
        );
        System.out.println(res1);  // >>> [true, true, true, true]

        boolean res2 = jedis.bfExists("recorded_users", "cameron");
        System.out.println(res2);  // >>> true

        boolean res3 = jedis.bfExists("recorded_users", "kaitlyn");
        System.out.println(res3);  // >>> false

code:
res1 = r.bf().madd("recorded_users", "andy", "cameron", "david", "michelle")
print(res1)  # >>> [1, 1, 1, 1]

res2 = r.bf().exists("recorded_users", "cameron")
print(res2)  # >>> 1

res3 = r.bf().exists("recorded_users", "kaitlyn")
print(res3)  # >>> 0

code:
bool res4 = db.CF().Add("other_users", "paolo");
        Console.WriteLine(res4); // >>> true

        bool res5 = db.CF().Add("other_users", "kaitlyn");
        Console.WriteLine(res5); // >>> true

        bool res6 = db.CF().Add("other_users", "rachel");
        Console.WriteLine(res6); // >>> true

        bool[] res7 = db.CF().MExists("other_users", "paolo", "rachel", "andy");
        Console.WriteLine(string.Join(", ", res7));
        // >>> true, true, false

        bool res8 = db.CF().Del("other_users", "paolo");
        Console.WriteLine(res8); // >>> true

        bool res9 = db.CF().Exists("other_users", "paolo");
        Console.WriteLine(res9); // >>> false

code:
res4, err := rdb.CFAdd(ctx, "other_users", "paolo").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res4) // >>> true

	res5, err := rdb.CFAdd(ctx, "other_users", "kaitlyn").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res5) // >>> true

	res6, err := rdb.CFAdd(ctx, "other_users", "rachel").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res6) // >>> true

	res7, err := rdb.CFMExists(ctx,
		"other_users", "paolo", "rachel", "andy",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res7) // >>> [true true false]

	res8, err := rdb.CFDel(ctx, "other_users", "paolo").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res8) // >>> true

	res9, err := rdb.CFExists(ctx, "other_users", "paolo").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res9) // >>> false

code:
boolean res4 = jedis.cfAdd("other_users", "paolo");
        System.out.println(res4);  // >>> true

        boolean res5 = jedis.cfAdd("other_users", "kaitlyn");
        System.out.println(res5);  // >>> true

        boolean res6 = jedis.cfAdd("other_users", "rachel");
        System.out.println(res6);  // >>> true

        List<Boolean> res7 = jedis.cfMExists(
            "other_users",
            "paolo", "rachel", "andy"
        );
        System.out.println(res7);  // >>> [true, true, false]

        boolean res8 = jedis.cfDel("other_users", "paolo");
        System.out.println(res8);  // >>> true

        boolean res9 = jedis.cfExists("other_users", "paolo");
        System.out.println(res9);  // >>> false

code:
res4 = r.cf().add("other_users", "paolo")
print(res4)  # >>> 1

res5 = r.cf().add("other_users", "kaitlyn")
print(res5)  # >>> 1

res6 = r.cf().add("other_users", "rachel")
print(res6)  # >>> 1

res7 = r.cf().mexists("other_users", "paolo", "rachel", "andy")
print(res7)  # >>> [1, 1, 0]

res8 = r.cf().delete("other_users", "paolo")
print(res8)  # >>> 1

res9 = r.cf().exists("other_users", "paolo")
print(res9)  # >>> 0

code:
bool res10 = db.HyperLogLogAdd(
            "group:1",
            ["andy", "cameron", "david"]
        );
        Console.WriteLine(res10); // >>> true

        long res11 = db.HyperLogLogLength("group:1");
        Console.WriteLine(res11); // >>> 3

        bool res12 = db.HyperLogLogAdd(
            "group:2",
            ["kaitlyn", "michelle", "paolo", "rachel"]
        );
        Console.WriteLine(res12); // >>> true

        long res13 = db.HyperLogLogLength("group:2");
        Console.WriteLine(res13); // >>> 4

        db.HyperLogLogMerge(
            "both_groups",
            "group:1", "group:2"
        );

        long res14 = db.HyperLogLogLength("both_groups");
        Console.WriteLine(res14); // >>> 7

section_id: set-cardinality

code:
res10, err := rdb.PFAdd(
		ctx,
		"group:1",
		"andy", "cameron", "david",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res10) // >>> 1

	res11, err := rdb.PFCount(ctx, "group:1").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res11) // >>> 3

	res12, err := rdb.PFAdd(ctx,
		"group:2",
		"kaitlyn", "michelle", "paolo", "rachel",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res12) // >>> 1

	res13, err := rdb.PFCount(ctx, "group:2").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res13) // >>> 4

	res14, err := rdb.PFMerge(
		ctx,
		"both_groups",
		"group:1", "group:2",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res14) // >>> OK

	res15, err := rdb.PFCount(ctx, "both_groups").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res15) // >>> 7

code:
long res10 = jedis.pfadd("group:1", "andy", "cameron", "david");
        System.out.println(res10);  // >>> 1

        long res11 = jedis.pfcount("group:1");
        System.out.println(res11);  // >>> 3

        long res12 = jedis.pfadd(
            "group:2",
            "kaitlyn", "michelle", "paolo", "rachel"
        );
        System.out.println(res12);  // >>> 1

        long res13 = jedis.pfcount("group:2");
        System.out.println(res13);  // >>> 4

        String res14 = jedis.pfmerge("both_groups", "group:1", "group:2");
        System.out.println(res14);  // >>> OK

        long res15 = jedis.pfcount("both_groups");
        System.out.println(res15);  // >>> 7

code:
res10 = r.pfadd("group:1", "andy", "cameron", "david")
print(res10)  # >>> 1

res11 = r.pfcount("group:1")
print(res11)  # >>> 3

res12 = r.pfadd("group:2", "kaitlyn", "michelle", "paolo", "rachel")
print(res12)  # >>> 1

res13 = r.pfcount("group:2")
print(res13)  # >>> 4

res14 = r.pfmerge("both_groups", "group:1", "group:2")
print(res14)  # >>> True

res15 = r.pfcount("both_groups")
print(res15)  # >>> 7

code:
// Specify that you want to keep the counts within 0.01
        // (0.1%) of the true value with a 0.005 (0.05%) chance
        // of going outside this limit.
        bool res15 = db.CMS().InitByProb("items_sold", 0.01, 0.005);
        Console.WriteLine(res15); // >>> true

        long[] res16 = db.CMS().IncrBy(
            "items_sold",
            [
                new("bread", 300),
                new("tea", 200),
                new("coffee", 200),
                new("beer", 100)
            ]
        );
        Console.WriteLine(string.Join(", ", res16));
        // >>> 300, 200, 200, 100

        long[] res17 = db.CMS().IncrBy(
            "items_sold",
            [
                new("bread", 100),
                new("coffee", 150)
            ]
        );
        Console.WriteLine(string.Join(", ", res17));
        // >>> 400, 350

        long[] res18 = db.CMS().Query(
            "items_sold",
            "bread", "tea", "coffee", "beer"
        );
        Console.WriteLine(string.Join(", ", res18));
        // >>> 400, 200, 350, 100

section_id: frequency

code:
// Specify that you want to keep the counts within 0.01
	// (0.1%) of the true value with a 0.005 (0.05%) chance
	// of going outside this limit.
	res16, err := rdb.CMSInitByProb(ctx, "items_sold", 0.01, 0.005).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res16) // >>> OK

	// The parameters for `CMSIncrBy()` are two lists. The count
	// for each item in the first list is incremented by the
	// value at the same index in the second list.
	res17, err := rdb.CMSIncrBy(ctx, "items_sold",
		"bread", 300,
		"tea", 200,
		"coffee", 200,
		"beer", 100,
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res17) // >>> [300 200 200 100]

	res18, err := rdb.CMSIncrBy(ctx, "items_sold",
		"bread", 100,
		"coffee", 150,
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res18) // >>> [400 350]

	res19, err := rdb.CMSQuery(ctx,
		"items_sold",
		"bread", "tea", "coffee", "beer",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res19) // >>> [400 200 350 100]

code:
// Specify that you want to keep the counts within 0.01
        // (0.1%) of the true value with a 0.005 (0.05%) chance
        // of going outside this limit.
        String res16 = jedis.cmsInitByProb("items_sold", 0.01, 0.005);
        System.out.println(res16);  // >>> OK

        Map<String, Long> firstItemIncrements = new HashMap<>();
        firstItemIncrements.put("bread", 300L);
        firstItemIncrements.put("tea", 200L);
        firstItemIncrements.put("coffee", 200L);
        firstItemIncrements.put("beer", 100L);

        List<Long> res17 = jedis.cmsIncrBy("items_sold",
            firstItemIncrements
        );
        res17.sort(null);
        System.out.println();  // >>> [100, 200, 200, 300]

        Map<String, Long> secondItemIncrements = new HashMap<>();
        secondItemIncrements.put("bread", 100L);
        secondItemIncrements.put("coffee", 150L);

        List<Long> res18 = jedis.cmsIncrBy("items_sold",
            secondItemIncrements
        );
        res18.sort(null);
        System.out.println(res18);  // >>> [350, 400]

        List<Long> res19 = jedis.cmsQuery(
            "items_sold",
            "bread", "tea", "coffee", "beer"
        );
        res19.sort(null);
        System.out.println(res19);  // >>> [100, 200, 350, 400]

code:
# Specify that you want to keep the counts within 0.01
# (1%) of the true value with a 0.005 (0.5%) chance
# of going outside this limit.
res16 = r.cms().initbyprob("items_sold", 0.01, 0.005)
print(res16)  # >>> True

# The parameters for `incrby()` are two lists. The count
# for each item in the first list is incremented by the
# value at the same index in the second list.
res17 = r.cms().incrby(
    "items_sold",
    ["bread", "tea", "coffee", "beer"],  # Items sold
    [300, 200, 200, 100]
)
print(res17)  # >>> [300, 200, 200, 100]

res18 = r.cms().incrby(
    "items_sold",
    ["bread", "coffee"],
    [100, 150]
)
print(res18)  # >>> [400, 350]

res19 = r.cms().query("items_sold", "bread", "tea", "coffee", "beer")
print(res19)  # >>> [400, 200, 350, 100]

code:
bool res19 = db.TDIGEST().Create("male_heights");
        Console.WriteLine(res19); // >>> true

        bool res20 = db.TDIGEST().Add(
            "male_heights",
            175.5, 181, 160.8, 152, 177, 196, 164
        );
        Console.WriteLine(res20); // >>> true

        double res21 = db.TDIGEST().Min("male_heights");
        Console.WriteLine(res21); // >>> 152.0

        double res22 = db.TDIGEST().Max("male_heights");
        Console.WriteLine(res22); // >>> 196.0

        double[] res23 = db.TDIGEST().Quantile("male_heights", 0.75);
        Console.WriteLine(string.Join(", ", res23)); // >>> 181.0

        // Note that the CDF value for 181.0 is not exactly
        // 0.75. Both values are estimates.
        double[] res24 = db.TDIGEST().CDF("male_heights", 181.0);
        Console.WriteLine(string.Join(", ", res24)); // >>> 0.7857142857142857

        bool res25 = db.TDIGEST().Create("female_heights");
        Console.WriteLine(res25); // >>> true

        bool res26 = db.TDIGEST().Add(
            "female_heights",
            155.5, 161, 168.5, 170, 157.5, 163, 171
        );
        Console.WriteLine(res26); // >>> true

        double[] res27 = db.TDIGEST().Quantile("female_heights", 0.75);
        Console.WriteLine(string.Join(", ", res27)); // >>> 170.0

        // Specify 0 for `compression` and false for `override`.
        bool res28 = db.TDIGEST().Merge(
            "all_heights", 0, false, "male_heights", "female_heights"
        );
        Console.WriteLine(res28); // >>> true

        double[] res29 = db.TDIGEST().Quantile("all_heights", 0.75);
        Console.WriteLine(string.Join(", ", res29)); // >>> 175.5

section_id: quantiles

code:
res20, err := rdb.TDigestCreate(ctx, "male_heights").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res20) // >>> OK

	res21, err := rdb.TDigestAdd(ctx, "male_heights",
		175.5, 181, 160.8, 152, 177, 196, 164,
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res21) // >>> OK

	res22, err := rdb.TDigestMin(ctx, "male_heights").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println(res22) // >>> 152

	res23, err := rdb.TDigestMax(ctx, "male_heights").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res23) // >>> 196

	res24, err := rdb.TDigestQuantile(ctx, "male_heights", 0.75).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res24) // >>> [181]

	// Note that the CDF value for 181 is not exactly
	// 0.75. Both values are estimates.
	res25, err := rdb.TDigestCDF(ctx, "male_heights", 181).Result()

	if err != nil {
		panic(err)
	}

	fmt.Printf("%.4f\n", res25[0]) // >>> 0.7857

	res26, err := rdb.TDigestCreate(ctx, "female_heights").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res26) // >>> OK

	res27, err := rdb.TDigestAdd(ctx, "female_heights",
		155.5, 161, 168.5, 170, 157.5, 163, 171,
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res27) // >>> OK

	res28, err := rdb.TDigestQuantile(ctx, "female_heights", 0.75).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res28) // >>> [170]

	res29, err := rdb.TDigestMerge(ctx, "all_heights",
		nil,
		"male_heights", "female_heights",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res29) // >>> OK

	res30, err := rdb.TDigestQuantile(ctx, "all_heights", 0.75).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res30) // >>> [175.5]

code:
String res20 = jedis.tdigestCreate("male_heights");
        System.out.println(res20);  // >>> OK

        String res21 = jedis.tdigestAdd("male_heights",
            175.5, 181, 160.8, 152, 177, 196, 164);
        System.out.println(res21);  // >>> OK

        double res22 = jedis.tdigestMin("male_heights");
        System.out.println(res22);  // >>> 152.0

        double res23 = jedis.tdigestMax("male_heights");
        System.out.println(res23);  // >>> 196.0

        List<Double> res24 = jedis.tdigestQuantile("male_heights", 0.75);
        System.out.println(res24);  // >>> [181.0]

        // Note that the CDF value for 181 is not exactly 0.75.
        // Both values are estimates.
        List<Double> res25 = jedis.tdigestCDF("male_heights", 181);
        System.out.println(res25);  // >>> [0.7857142857142857]

        String res26 = jedis.tdigestCreate("female_heights");
        System.out.println(res26);  // >>> OK

        String res27 = jedis.tdigestAdd("female_heights",
            155.5, 161, 168.5, 170, 157.5, 163, 171);
        System.out.println(res27);  // >>> OK

        List<Double> res28 = jedis.tdigestQuantile("female_heights", 0.75);
        System.out.println(res28);  // >>> [170.0]

        String res29 = jedis.tdigestMerge(
            "all_heights",
            "male_heights", "female_heights"
        );
        System.out.println(res29);  // >>> OK
        List<Double> res30 = jedis.tdigestQuantile("all_heights", 0.75);
        System.out.println(res30);  // >>> [175.5]

code:
res20 = r.tdigest().create("male_heights")
print(res20)  # >>> True

res21 = r.tdigest().add(
    "male_heights",
    [175.5, 181, 160.8, 152, 177, 196, 164]
)
print(res21)  # >>> OK

res22 = r.tdigest().min("male_heights")
print(res22)  # >>> 152.0

res23 = r.tdigest().max("male_heights")
print(res23)  # >>> 196.0

res24 = r.tdigest().quantile("male_heights", 0.75)
print(res24)  # >>> 181

# Note that the CDF value for 181 is not exactly
# 0.75. Both values are estimates.
res25 = r.tdigest().cdf("male_heights", 181)
print(res25)  # >>> [0.7857142857142857]

res26 = r.tdigest().create("female_heights")
print(res26)  # >>> True

res27 = r.tdigest().add(
    "female_heights",
    [155.5, 161, 168.5, 170, 157.5, 163, 171]
)
print(res27)  # >>> OK

res28 = r.tdigest().quantile("female_heights", 0.75)
print(res28)  # >>> [170]

res29 = r.tdigest().merge(
    "all_heights", 2, "male_heights", "female_heights"
)
print(res29)  # >>> OK

res30 = r.tdigest().quantile("all_heights", 0.75)
print(res30)  # >>> [175.5]

code:
bool res30 = db.TOPK().Reserve("top_3_songs", 3, 7, 8, 0.9);
        Console.WriteLine(res30); // >>> true

        RedisResult[] res31 = db.TOPK().IncrBy(
            "top_3_songs",
            new Tuple<RedisValue, long>[] {
                new("Starfish Trooper", 3000),
                new("Only one more time", 1850),
                new("Rock me, Handel", 1325),
                new("How will anyone know?", 3890),
                new("Average lover", 4098),
                new("Road to everywhere", 770)
            }
        );
        Console.WriteLine(
            string.Join(
                ", ",
                string.Join(
                    ", ",
                    res31.Select(
                        r => $"{(r.IsNull ? "Null" : r)}"
                    )
                )
            )
        );
        // >>> Null, Null, Null, Rock me, Handel, Only one more time, Null

        RedisResult[] res32 = db.TOPK().List("top_3_songs");
        Console.WriteLine(
            string.Join(
                ", ",
                string.Join(
                    ", ",
                    res32.Select(
                        r => $"{(r.IsNull ? "Null" : r)}"
                    )
                )
            )
        );
        // >>> Average lover, How will anyone know?, Starfish Trooper

        bool[] res33 = db.TOPK().Query(
            "top_3_songs",
            "Starfish Trooper", "Road to everywhere"
        );
        Console.WriteLine(string.Join(", ", res33));
        // >>> true, false

section_id: ranking

code:
// Create a TopK filter that keeps track of the top 3 items
	res31, err := rdb.TopKReserve(ctx, "top_3_songs", 3).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res31) // >>> OK

	// Add some items to the filter
	res32, err := rdb.TopKIncrBy(ctx,
		"top_3_songs",
		"Starfish Trooper", 3000,
		"Only one more time", 1850,
		"Rock me, Handel", 1325,
		"How will anyone know?", 3890,
		"Average lover", 4098,
		"Road to everywhere", 770,
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res32)
	// >>> [   Rock me, Handel Only one more time ]

	res33, err := rdb.TopKList(ctx, "top_3_songs").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res33)
	// >>> [Average lover How will anyone know? Starfish Trooper]

	// Query the count for specific items
	res34, err := rdb.TopKQuery(
		ctx,
		"top_3_songs",
		"Starfish Trooper", "Road to everywhere",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res34) // >>> [true false]

code:
String res31 = jedis.topkReserve("top_3_songs", 3L, 2000L, 7L, 0.925D);
        System.out.println(res31);  // >>> OK

        Map<String, Long> songIncrements = new HashMap<>();
        songIncrements.put("Starfish Trooper", 3000L);
        songIncrements.put("Only one more time", 1850L);
        songIncrements.put("Rock me, Handel", 1325L);
        songIncrements.put("How will anyone know?", 3890L);
        songIncrements.put("Average lover", 4098L);
        songIncrements.put("Road to everywhere", 770L);

        List<String> res32 = jedis.topkIncrBy("top_3_songs",
            songIncrements
        );
        System.out.println(res32);
        // >>> [null, null, null, null, null, Rock me, Handel]

        List<String> res33 = jedis.topkList("top_3_songs");
        System.out.println(res33);
        // >>> [Average lover, How will anyone know?, Starfish Trooper]

        List<Boolean> res34 = jedis.topkQuery("top_3_songs",
            "Starfish Trooper", "Road to everywhere"
        );
        System.out.println(res34);
        // >>> [true, false]

code:
# The `reserve()` method creates the Top-K object with
# the given key. The parameters are the number of items
# in the ranking and values for `width`, `depth`, and
# `decay`, described in the Top-K reference page.
res31 = r.topk().reserve("top_3_songs", 3, 7, 8, 0.9)
print(res31)  # >>> True

# The parameters for `incrby()` are two lists. The count
# for each item in the first list is incremented by the
# value at the same index in the second list.
res32 = r.topk().incrby(
    "top_3_songs",
    [
        "Starfish Trooper",
        "Only one more time",
        "Rock me, Handel",
        "How will anyone know?",
        "Average lover",
        "Road to everywhere"
    ],
    [
        3000,
        1850,
        1325,
        3890,
        4098,
        770
    ]
)
print(res32)
# >>> [None, None, None, 'Rock me, Handel', 'Only one more time', None]

res33 = r.topk().list("top_3_songs")
print(res33)
# >>> ['Average lover', 'How will anyone know?', 'Starfish Trooper']

res34 = r.topk().query(
    "top_3_songs", "Starfish Trooper", "Road to everywhere"
)
print(res34)  # >>> [1, 0]


title: Production usage

title: Overview

text:
This guide offers recommendations to get the best reliability and
performance in your production environment.

title: Checklist

text:
Each item in the checklist below links to the section
for a recommendation. Use the checklist icons to record your
progress in implementing the recommendations.

[code example]

title: Recommendations

text:
The sections below offer recommendations for your production environment. Some
of them may not apply to your particular use case.

title: Event handling

text:
The `ConnectionMultiplexer` class publishes several different types of
[events](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/)
for situations such as configuration changes and connection failures.
Use these events to record server activity in a log, which you can then use
to monitor performance and diagnose problems when they occur.
See
the StackExchange.Redis
[Events](https://stackexchange.github.io/StackExchange.Redis/Events)
page for the full list of events.

#### Server notification events

Some servers (such as Azure Cache for Redis) send notification events shortly
before scheduled maintenance is due to happen. You can use code like the
following to respond to these events (see the
[StackExchange.Redis](https://stackexchange.github.io/StackExchange.Redis/ServerMaintenanceEvent)
docs for the full list of supported events). For example, you could
inform users who try to connect that service is temporarily unavailable
rather than letting them run into errors.

[code example]

title: Timeouts

text:
If a network or server error occurs while your code is opening a
connection or issuing a command, it can end up hanging indefinitely.
To prevent this, `NRedisStack` sets timeouts for socket
reads and writes and for opening connections.

By default, the timeout is five seconds for all operations, but
you can set the time (in milliseconds) separately for connections
and commands using the `ConnectTimeout`, `SyncTimeout`, and
`AsyncTimeout` configuration options:

[code example]

The default timeouts are a good starting point, but you may be able
to improve performance by adjusting the values to suit your use case.

title: Exception handling

text:
Redis handles many errors using return values from commands, but there
are also situations where exceptions can be thrown. In production code,
you should handle exceptions as they occur. The list below describes some
the most common Redis exceptions:

- `RedisConnectionException`: Thrown when a connection attempt fails.
- `RedisTimeoutException`: Thrown when a command times out.
- `RedisCommandException`: Thrown when you issue an invalid command.
- `RedisServerException`: Thrown when you attempt an invalid operation
  (for example, trying to access a
  [stream entry]()
  using an invalid ID).

See [Error handling]()
for more information on handling exceptions.

title: Retries

text:
During the initial `ConnectionMultiplexer.Connect()` call, `NRedisStack` will
keep trying to connect if the first attempt fails. By default, it will make
three attempts, but you can configure the number of retries using the
`ConnectRetry` configuration option:

[code example]

After the initial `Connect()` call is successful, `NRedisStack` will
automatically attempt to reconnect if the connection is lost. You can
specify a reconnection strategy with the `ReconnectRetryPolicy` configuration
option. `NRedisStack` provides two built-in classes that implement
reconnection strategies:

- `ExponentialRetry`: (Default) Uses an
    [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff)
    strategy, where you specify an increment to the delay between successive
    attempts and, optionally, a maximum delay, both in milliseconds.
-   `LinearRetry`: Uses a linear backoff strategy with a fixed delay between
    attempts, in milliseconds.

The example below shows how to use the `ExponentialRetry` class:

[code example]

You can also implement your own custom retry policy by creating a class
that implements the `IReconnectRetryPolicy` interface.

`NRedisStack` doesn't provide an automated retry mechanism for commands, but
you can implement your own retry logic in your application code. Use
a loop with a `try`/`catch` block to catch `RedisConnectionException` and
`RedisTimeoutException` exceptions and then retry the command after a
suitable delay, as shown in the example below:

[code example]

code:
- [ ] [Event handling](#event-handling)
- [ ] [Timeouts](#timeouts)
- [ ] [Exception handling](#exception-handling)
- [ ] [Retries](#retries)

section_id: checklist

code:
using NRedisStack;
using StackExchange.Redis;

ConnectionMultiplexer muxer = ConnectionMultiplexer.Connect("localhost:6379");

muxer.ServerMaintenanceEvent += (object sender, ServerMaintenanceEvent e) => {
    // Identify the event and respond to it here.
    Console.WriteLine($"Maintenance event: {e.RawMessage}");
};

section_id: event-handling

code:
var muxer = ConnectionMultiplexer.Connect(new ConfigurationOptions {
    ConnectTimeout = 1000,  // 1 second timeout for connections.
    SyncTimeout = 2000,     // 2 seconds for synchronous commands.
    AsyncTimeout = 3000     // 3 seconds for asynchronous commands.
        .
        .
});

var db = muxer.GetDatabase();

section_id: timeouts

code:
var muxer = ConnectionMultiplexer.Connect(new ConfigurationOptions {
    ConnectRetry = 5,  // Retry up to five times.
        .
        .
});

section_id: retries

code:
var muxer = ConnectionMultiplexer.Connect(new ConfigurationOptions {
    // 500ms increment per attempt, max 2000ms.
    ReconnectRetryPolicy = new ExponentialRetry(500, 2000),
        .
        .
});

code:
const int MAX_RETRIES = 3;

for (int i = 0; i < MAX_RETRIES; i++) {
    try {
        string value = db.StringGet("foo");
        break;
    } catch (RedisConnectionException) {
        // Wait before retrying.
        Thread.Sleep(500 * (i + 1));
    } catch (RedisTimeoutException) {
        // Wait before retrying.
        Thread.Sleep(500 * (i + 1));
    }
}


title: Index and query documents

title: Initialize

text:
Make sure that you have [Redis Open Source]()
or another Redis server available. Also install the
[`NRedisStack`]() client library if you
haven't already done so.

Add the following dependencies:

#### Code Examples

Foundational: Import required libraries for Redis Search, JSON operations, and search functionality

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

title: Create data

text:
Create some test data to add to the database:

#### Code Examples

Foundational: Define sample user data structures for indexing and querying

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

title: Add the index

text:
Connect to your Redis database. The code below shows the most
basic connection but see
[Connect to the server]()
to learn more about the available connection options.

#### Code Examples

Foundational: Establish a connection to a Redis server for query operations

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

Delete any existing index called `idx:users` and any keys that start with `user:`.

#### Code Examples

Foundational: Clean up existing indexes and documents to prepare for fresh example data

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

Create an index. In this example, only JSON documents with the key prefix `user:` are indexed. For more information, see [Query syntax]().

#### Code Examples

Foundational: Create a search index for JSON documents with field definitions and key prefix filtering

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

title: Add the data

text:
Add the three sets of user data to the database as
[JSON]() objects.
If you use keys with the `user:` prefix then Redis will index the
objects automatically as you add them:

#### Code Examples

Foundational: Store JSON documents in Redis with automatic indexing based on key prefix

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

title: Query the data

text:
You can now use the index to search the JSON objects. The
[query]()
below searches for objects that have the text "Paul" in any field
and have an `age` value in the range 30 to 40:

#### Code Examples

Query with filters: Search JSON documents using text matching and numeric range filters to find specific records

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

Specify query options to return only the `city` field:

#### Code Examples

Query with field projection: Retrieve only specific fields from search results to reduce data transfer

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

Use an
[aggregation query]()
to count all users in each city.

#### Code Examples

Aggregation queries: Use GROUP BY and COUNT operations to summarize and analyze indexed data

**Difficulty:** Advanced

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

title: Differences with hash documents

text:
Indexing for hash documents is very similar to JSON indexing but you
need to specify some slightly different options.

When you create the schema for a hash index, you don't need to
add aliases for the fields, since you use the basic names to access
the fields anyway. Also, you must set the `On` option to `IndexDataType.HASH`
in the `FTCreateParams` object when you create the index. The code below shows
these changes with a new index called `hash-idx:users`, which is otherwise the
same as the `idx:users` index used for JSON documents in the previous examples.

First, delete any existing index called `hash-idx:users` and any keys that start with `huser:`.

#### Code Examples

Foundational: Clean up existing hash indexes and documents to prepare for fresh example data

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

Now create the new index:

#### Code Examples

Foundational: Create a search index for hash documents with HASH index type and field definitions

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

You use [`HashSet()`]() to add the hash
documents instead of [`JSON.Set()`]().
Also, you must add the fields as key-value pairs instead of combining them
into a single object.

#### Code Examples

Foundational: Store hash documents in Redis with automatic indexing based on key prefix

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

The query commands work the same here for hash as they do for JSON (but
the name of the hash index is different). The format of the result is
almost the same except that the fields are returned directly in the
`Document` object of the result (for JSON, the fields are all enclosed
in a string under the key `json`):

#### Code Examples

Query with filters: Search hash documents using text matching and numeric range filters (same as JSON queries)

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous)

**C# (Synchronous):**

[code example]

title: More information

text:
See the [Redis Search]() docs
for a full description of all query features with examples.

code:
using NRedisStack.RedisStackCommands;
using NRedisStack.Search;
using NRedisStack.Search.Aggregation;
using NRedisStack.Search.Literals.Enums;
using StackExchange.Redis;

section_id: initialize

code:
var user1 = new
        {
            name = "Paul John",
            email = "paul.john@example.com",
            age = 42,
            city = "London"
        };

        var user2 = new
        {
            name = "Eden Zamir",
            email = "eden.zamir@example.com",
            age = 29,
            city = "Tel Aviv"
        };

        var user3 = new
        {
            name = "Paul Zamir",
            email = "paul.zamir@example.com",
            age = 35,
            city = "Tel Aviv"
        };

section_id: create-data

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

section_id: add-the-index

code:
db.KeyDelete(["user:1", "user:2", "user:3"]);
        try { db.FT().DropIndex("idx:users"); } catch { }

code:
var schema = new Schema()
            .AddTextField(new FieldName("$.name", "name"))
            .AddTagField(new FieldName("$.city", "city"))
            .AddNumericField(new FieldName("$.age", "age"));

        bool indexCreated = db.FT().Create(
            "idx:users",
            new FTCreateParams()
                .On(IndexDataType.JSON)
                .Prefix("user:"),
            schema
        );

code:
bool user1Set = db.JSON().Set("user:1", "$", user1);
        bool user2Set = db.JSON().Set("user:2", "$", user2);
        bool user3Set = db.JSON().Set("user:3", "$", user3);

section_id: add-the-data

code:
SearchResult findPaulResult = db.FT().Search(
            "idx:users",
            new("Paul @age:[30 40]")
        );
        Console.WriteLine(string.Join(
            ", ",
            findPaulResult.Documents.Select(x => x["json"])
        ));
        // >>> {"name":"Paul Zamir","email":"paul.zamir@example.com", ...

section_id: query-the-data

code:
var citiesResult = db.FT().Search(
            "idx:users",
            new Query("Paul")
                .ReturnFields(new FieldName("$.city", "city"))
        );
        Console.WriteLine(string.Join(
            ", ",
            citiesResult.Documents.Select(x => x["city"]).OrderBy(x => x)
        ));
        // >>> London, Tel Aviv

code:
AggregationRequest aggRequest = new AggregationRequest("*")
            .GroupBy("@city", Reducers.Count().As("count"));

        AggregationResult aggResult = db.FT().Aggregate("idx:users", aggRequest);
        IReadOnlyList<Dictionary<string, RedisValue>> resultsList =
                                                        aggResult.GetResults();

        for (var i = 0; i < resultsList.Count; i++)
        {
            Dictionary<string, RedisValue> item = resultsList.ElementAt(i);
            Console.WriteLine($"{item["city"]} - {item["count"]}");
        }
        // >>> London - 1
        // >>> Tel Aviv - 2

code:
db.KeyDelete(["huser:1", "huser:2", "huser:3"]);
        try { db.FT().DropIndex("hash-idx:users"); } catch { }

section_id: differences-with-hash-documents

code:
var hashSchema = new Schema()
            .AddTextField("name")
            .AddTagField("city")
            .AddNumericField("age");

        bool hashIndexCreated = db.FT().Create(
            "hash-idx:users",
            new FTCreateParams()
                .On(IndexDataType.HASH)
                .Prefix("huser:"),
            hashSchema
        );

code:
db.HashSet("huser:1", [
            new("name", "Paul John"),
            new("email", "paul.john@example.com"),
            new("age", 42),
            new("city", "London")
        ]);

        db.HashSet("huser:2", [
            new("name", "Eden Zamir"),
            new("email", "eden.zamir@example.com"),
            new("age", 29),
            new("city", "Tel Aviv")
        ]);

        db.HashSet("huser:3", [
            new("name", "Paul Zamir"),
            new("email", "paul.zamir@example.com"),
            new("age", 35),
            new("city", "Tel Aviv")
        ]);

code:
SearchResult findPaulHashResult = db.FT().Search(
            "hash-idx:users",
            new("Paul @age:[30 40]")
        );

        foreach (Document doc in findPaulHashResult.Documents)
        {
            Console.WriteLine(
                $"Name: {doc["name"]}, email: {doc["email"]}, " +
                $"age: {doc["age"]}, city:{doc["city"]}"
            );
        }
        // >>> Name: Paul Zamir, email: paul.zamir@example.com, age: 35, ...


title: Pipelines and transactions

title: Execute a pipeline

text:
To execute commands in a pipeline, you first create a pipeline object
and then add commands to it using methods that resemble the *asynchronous*
versions of the standard command methods
(for example, `StringSetAsync()` and `StringGetAsync()`). The commands are
buffered in the pipeline and only execute when you call the `Execute()`
method on the pipeline object.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python, Rust (Synchronous)

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

**Rust (Synchronous):**

[code example]

title: Execute a transaction

text:
A transaction works in a similar way to a pipeline. Create an
instance of the `Transaction` class, call async command methods
on that object, and then call the transaction object's
`Execute()` method to execute it.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python, Rust (Synchronous)

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

**Rust (Synchronous):**

[code example]

title: Watch keys for changes

text:
Redis supports *optimistic locking* to avoid inconsistent updates
to different keys. The basic idea is to watch for changes to any
keys that you use in a transaction while you are are processing the
updates. If the watched keys do change, you must restart the updates
with the latest data from the keys. See
[Transactions]()
for more information about optimistic locking.

The approach to optimistic locking that other clients use
(adding the [`WATCH`]() command
explicitly to a transaction) doesn't work well with the
[multiplexing]()
system that `NRedisStack` uses.
Instead, `NRedisStack` relies on conditional execution of commands
to get a similar effect.

Use the `AddCondition()` method to abort a transaction if a particular
condition doesn't hold throughout its execution. If the transaction
does abort then the `Execute()` method returns a `false` value,
but otherwise returns `true`.

For example, the `KeyNotExists` condition aborts the transaction
if a specified key exists or is added by another client while the
transaction executes:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python, Rust (Synchronous)

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

**Rust (Synchronous):**

[code example]

You can also use a `When` condition on certain individual commands to
specify that they only execute when a certain condition holds
(for example, the command does not change an existing key).
See
[Conditional execution]()
for a full description of transaction and command conditions.

code:
var pipeline = new Pipeline(db);

        for (int i = 0; i < 5; i++)
        {
            _ = pipeline.Db.StringSetAsync($"seat:{i}", $"#{i}");
        }
        pipeline.Execute();

        var resp1 = db.StringGet("seat:0");
        Console.WriteLine(resp1); // >>> #0

        var resp2 = db.StringGet("seat:3");
        Console.WriteLine(resp2); // >>> #3

        var resp3 = db.StringGet("seat:4");
        Console.WriteLine(resp2); // >>> #4

section_id: execute-a-pipeline

code:
pipe := rdb.Pipeline()

	for i := 0; i < 5; i++ {
		pipe.Set(ctx, fmt.Sprintf("seat:%v", i), fmt.Sprintf("#%v", i), 0)
	}

	cmds, err := pipe.Exec(ctx)

	if err != nil {
		panic(err)
	}

	for _, c := range cmds {
		fmt.Printf("%v;", c.(*redis.StatusCmd).Val())
	}

	fmt.Println("")
	// >>> OK;OK;OK;OK;OK;

	pipe = rdb.Pipeline()

	get0Result := pipe.Get(ctx, "seat:0")
	get3Result := pipe.Get(ctx, "seat:3")
	get4Result := pipe.Get(ctx, "seat:4")

	cmds, err = pipe.Exec(ctx)

	// The results are available only after the pipeline
	// has finished executing.
	fmt.Println(get0Result.Val()) // >>> #0
	fmt.Println(get3Result.Val()) // >>> #3
	fmt.Println(get4Result.Val()) // >>> #4

code:
// Make sure you close the pipeline after use to release resources
        // and return the connection to the pool.
        try (AbstractPipeline pipe = jedis.pipelined()) {

            for (int i = 0; i < 5; i++) {
                pipe.set(String.format("seat:%d", i), String.format("#%d", i));
            }

            pipe.sync();
        }

        try (AbstractPipeline pipe = jedis.pipelined()) {

            Response<String> resp0 = pipe.get("seat:0");
            Response<String> resp3 = pipe.get("seat:3");
            Response<String> resp4 = pipe.get("seat:4");

            pipe.sync();

            // Responses are available after the pipeline has executed.
            System.out.println(resp0.get()); // >>> #0
            System.out.println(resp3.get()); // >>> #3
            System.out.println(resp4.get()); // >>> #4

        }

code:
// Check the success of the pipeline without checking the results
        // individually.
        match redis::pipe()
            .set("seat:0", "#0")
            .set("seat:1", "#1")
            .set("seat:2", "#2")
            .set("seat:3", "#3")
            .set("seat:4", "#4")
            .exec(&mut r)
        {
            Ok(_) => {
                println!("Pipe executed successfully");
            },
            Err(e) => {
                println!("Error executing pipe: {e}");
                return;
            }
        };

        // Check the success of the pipeline and the results individually.
        let (seat_0, seat_1, seat_2, seat_3, seat_4) :
        (String, String, String, String, String) = match redis::pipe()
            .get("seat:0")
            .get("seat:1")
            .get("seat:2")
            .get("seat:3")
            .get("seat:4")
            .query(&mut r) {
                Ok(res) => res,
                Err(e) => {
                    println!("Error executing pipe: {e}");
                    return;
                }
            };

        println!("{seat_0}, {seat_1}, {seat_2}, {seat_3}, {seat_4}");
        // >>> #0, #1, #2, #3, #4

        // Use `ignore()` to ignore the result of specific commands.
        let (seat_5, seat_6, seat_7) :
            (String, String, String) = match redis::pipe()
            .set("seat:5", "#5").ignore()
            .set("seat:6", "#6").ignore()
            .set("seat:7", "#7").ignore()
            .get("seat:5")
            .get("seat:6")
            .get("seat:7")
            .query(&mut r) {
                Ok(res) => res,
                Err(e) => {
                    println!("Error executing pipe: {e}");
                    return;
                }
            };

        println!("{seat_5}, {seat_6}, {seat_7}");
        // >>> #5, #6, #7

code:
var trans = new Transaction(db);

        _ = trans.Db.StringIncrementAsync("counter:1", 1);
        _ = trans.Db.StringIncrementAsync("counter:2", 2);
        _ = trans.Db.StringIncrementAsync("counter:3", 3);

        trans.Execute();

        var resp4 = db.StringGet("counter:1");
        Console.WriteLine(resp4); // >>> 1

        var resp5 = db.StringGet("counter:2");
        Console.WriteLine(resp5); // >>> 2

        var resp6 = db.StringGet("counter:3");
        Console.WriteLine(resp6);  // >>> 3

section_id: execute-a-transaction

code:
trans := rdb.TxPipeline()

	trans.IncrBy(ctx, "counter:1", 1)
	trans.IncrBy(ctx, "counter:2", 2)
	trans.IncrBy(ctx, "counter:3", 3)

	cmds, err = trans.Exec(ctx)

	for _, c := range cmds {
		fmt.Println(c.(*redis.IntCmd).Val())
	}
	// >>> 1
	// >>> 2
	// >>> 3

code:
try ( AbstractTransaction trans = jedis.multi()) {

           trans.incrBy("counter:1", 1);
           trans.incrBy("counter:2", 2);
           trans.incrBy("counter:3", 3);

           trans.exec();
        }
        System.out.println(jedis.get("counter:1")); // >>> 1
        System.out.println(jedis.get("counter:2")); // >>> 2
        System.out.println(jedis.get("counter:3")); // >>> 3

code:
"""
Code samples for vector database quickstart pages:
    https://redis.io/docs/latest/develop/get-started/vector-database/
"""
import redis

r = redis.Redis(decode_responses=True)

pipe = r.pipeline()

for i in range(5):
    pipe.set(f"seat:{i}", f"#{i}")

set_5_result = pipe.execute()
print(set_5_result)  # >>> [True, True, True, True, True]

pipe = r.pipeline()

# "Chain" pipeline commands together.
get_3_result = pipe.get("seat:0").get("seat:3").get("seat:4").execute()
print(get_3_result)  # >>> ['#0', '#3', '#4']

r.set("shellpath", "/usr/syscmds/")

with r.pipeline() as pipe:
    # Repeat until successful.
    while True:
        try:
            # Watch the key we are about to change.
            pipe.watch("shellpath")

            # The pipeline executes commands directly (instead of
            # buffering them) from immediately after the `watch()`
            # call until we begin the transaction.
            current_path = pipe.get("shellpath")
            new_path = current_path + ":/usr/mycmds/"

            # Start the transaction, which will enable buffering
            # again for the remaining commands.
            pipe.multi()

            pipe.set("shellpath", new_path)

            pipe.execute()

            # The transaction succeeded, so break out of the loop.
            break
        except redis.WatchError:
            # The transaction failed, so continue with the next attempt.
            continue

get_path_result = r.get("shellpath")
print(get_path_result)  # >>> '/usr/syscmds/:/usr/mycmds/'

r.set("shellpath", "/usr/syscmds/")

def watched_sequence(pipe):
    current_path = pipe.get("shellpath")
    new_path = current_path + ":/usr/mycmds/"

    pipe.multi()

    pipe.set("shellpath", new_path)

trans_result = r.transaction(watched_sequence, "shellpath")
print(trans_result)  # True

get_path_result = r.get("shellpath")
print(get_path_result)  # >>> '/usr/syscmds/:/usr/mycmds/'

code:
match redis::pipe()
            .atomic()
            .incr("counter:1", 1)
            .incr("counter:2", 2)
            .incr("counter:3", 3)
            .exec(&mut r)
        {
            Ok(_) => {
                println!("Transaction executed successfully");
            },
            Err(e) => {
                println!("Error executing transaction: {e}");
                return;
            }
        };

code:
var watchedTrans = new Transaction(db);

        watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));

        _ = watchedTrans.Db.HashSetAsync(
            "customer:39182",
            [
                new("name", "David"),
                new("age", "27")
            ]
        );

        bool succeeded = watchedTrans.Execute();
        Console.WriteLine(succeeded); // >>> true

section_id: watch-keys-for-changes

code:
// Set initial value of `shellpath`.
	rdb.Set(ctx, "shellpath", "/usr/syscmds/", 0)

	const maxRetries = 1000

	// Retry if the key has been changed.
	for i := 0; i < maxRetries; i++ {
		err := rdb.Watch(ctx,
			func(tx *redis.Tx) error {
				currentPath, err := rdb.Get(ctx, "shellpath").Result()
				newPath := currentPath + ":/usr/mycmds/"

				_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
					pipe.Set(ctx, "shellpath", newPath, 0)
					return nil
				})

				return err
			},
			"shellpath",
		)

		if err == nil {
			// Success.
			break
		} else if err == redis.TxFailedErr {
			// Optimistic lock lost. Retry the transaction.
			continue
		} else {
			// Panic for any other error.
			panic(err)
		}
	}

	fmt.Println(rdb.Get(ctx, "shellpath").Val())
	// >>> /usr/syscmds/:/usr/mycmds/

code:
// Set initial value of `shellpath`.
        jedis.set("shellpath", "/usr/syscmds/");

        // Start the transaction and watch the key we are about to update.
        try (AbstractTransaction trans = jedis.transaction(false)) { // create a Transaction object without sending MULTI command
            trans.watch("shellpath"); // send WATCH command(s)
            trans.multi(); // send MULTI command

            String currentPath = jedis.get("shellpath");
            String newPath = currentPath + ":/usr/mycmds/";

            // Commands added to the `trans` object
            // will be buffered until `trans.exec()` is called.
            Response<String> setResult = trans.set("shellpath", newPath);
            List<Object> transResults = trans.exec();

            // The `exec()` call returns null if the transaction failed.
            if (transResults != null) {
                // Responses are available if the transaction succeeded.
                System.out.println(setResult.get()); // >>> OK

                // You can also get the results from the list returned by
                // `trans.exec()`.
                for (Object item: transResults) {
                    System.out.println(item);
                }
                // >>> OK

                System.out.println(jedis.get("shellpath"));
                // >>> /usr/syscmds/:/usr/mycmds/
            }
        }

code:
let key = "shellpath";
        let _: () = r.set(key, "/usr/syscmds/").unwrap();

        let Ok(_,): Result<((),), _> = redis::transaction(&mut r, &[key], |r, pipe| {
            let mut path: String = r.get(key).unwrap();
            path.push_str(":/usr/mycmds/");
            pipe.set(key, path).query(r)
        }) else {
            println!("Error executing transaction");
            return;
        };

        match r.get("shellpath") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");
                // >>> /usr/syscmds/:/usr/mycmds/
            },
            Err(e) => {
                println!("Error getting shellpath: {e}");
                return;
            }
        };