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

summary: Get your `redis-py` app ready for production

page_type: content

title: Overview

role: overview

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

title: Checklist

role: content

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: Client-side caching

text:
[Client-side caching]()
involves storing the results from read-only commands in a local cache. If the
same command is executed again later, the results can be obtained from the cache,
without contacting the server. This improves command execution time on the client,
while also reducing network traffic and server load. See
[Connect using client-side caching]()
for more information and example code.

title: Retries

text:
Redis connections and commands can often fail due to transient problems,
such as temporary network outages or timeouts. When this happens,
the operation will generally succeed after a few attempts, despite
failing the first time.

`redis-py` can retry commands automatically when
errors occur. From version 6.0.0 onwards, the default behavior is to
attempt a failed command three times.
The timing between successive attempts is calculated using
[exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff)
with some random "jitter" added to avoid two or more connections retrying
commands in sync with each other.

You can override the default behavior using an instance of the `Retry` class to
specify the number of times to retry after a failure along with your
own choice of backoff strategy.
Pass the `Retry` object in the `retry` parameter when you connect.
For example, the connection in the code below uses an exponential backoff strategy
(without jitter) that will make eight repeated attempts after a failure:

[code example]

A retry is triggered when a command throws any exception
listed in the `supported_errors` attribute of the `Retry` class.
By default, the list only includes `ConnectionError` and `TimeoutError`,
but you can set your own choice of exceptions when you create the
instance:

[code example]

You can also add extra exceptions to the default list using the `retry_on_error`
parameter when you connect:

[code example]

For a connection to a Redis cluster, you can specify a `retry` instance,
but the list of exceptions is not configurable and is always set
to `TimeoutError`, `ConnectionError`, and `ClusterDownError`.

title: Health checks

text:
If your code doesn't access the Redis server continuously then it
might be useful to make a "health check" periodically (perhaps once
every few seconds) to verify that the connection is working.
Set the `health_check_interval` parameter during
a connection (with either `Redis` or `ConnectionPool`) to specify
an integer number of seconds. If the connection remains idle for
longer than this interval, it will automatically issue a
[`PING`]() command and check the
response before continuing with any client commands.

[code example]

Health checks help to detect problems as soon as possible without
waiting for a user to report them. Note that health checks, like
other commands, will be [retried](#retries) using the strategy
that you specified for the connection.

title: Exception handling

role: errors

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 wherever they can occur.

Import the exceptions you need to check from the `redis.exceptions`
module. The list below describes some of the most common exceptions.

- `ConnectionError`: Thrown when a connection attempt fails
  (for example, when connection parameters are invalid or the server
  is unavailable) and sometimes when a [health check](#health-checks)
  fails. There is also a subclass, `AuthenticationError`, specifically
  for authentication failures.
- `ResponseError`: Thrown when you attempt an operation that has no valid
  response. Examples include executing a command on the wrong type of key
  (as when you try an
  ['LPUSH']()
  command on a string key), creating an
  [index]()
  with a name that already exists, and using an invalid ID for a
  [stream entry]().
- `TimeoutError`: Thrown when a timeout persistently happens for a command,
  despite any [retries](#retries).
- `WatchError`: Thrown when a
  [watched key]() is
  modified during a transaction.

title: Timeouts

text:
After you issue a command or a connection attempt, the client will wait
for a response from the server. If the server doesn't respond within a
certain time limit, the client will throw a `TimeoutError`. By default,
the timeout happens after 10 seconds for both connections and commands, but you
can set your own timeouts using the `socket_connect_timeout` and `socket_timeout` parameters
when you connect:

[code example]

Take care to set the timeouts to appropriate values for your use case.
If you use timeouts that are too short, then `redis-py` might retry
commands that would have succeeded if given more time. However, if the
timeouts are too long, your app might hang unnecessarily while waiting for a
response that will never arrive.

title: Smart client handoffs

text:
*Smart client handoffs (SCH)* is a feature of Redis Cloud and
Redis Software servers that lets them actively notify clients
about planned server maintenance shortly before it happens. This
lets a client take action to avoid disruptions in service.

See [Smart client handoffs]()
for more information about SCH and
[Connect using Smart client handoffs]()
for example code.

title: Monitor performance and errors

text:
`redis-py` supports [OpenTelemetry](https://opentelemetry.io/). This lets
you trace command execution and monitor your server's performance.
You can use this information to detect problems before they are reported
by users. See [Observability]()
for more information.

code:
- [ ] [Client-side caching](#client-side-caching)
- [ ] [Retries](#retries)
- [ ] [Health checks](#health-checks)
- [ ] [Exception handling](#exception-handling)
- [ ] [Timeouts](#timeouts)
- [ ] [Smart client handoffs](#smart-client-handoffs)
- [ ] [Monitor performance and errors](#monitor-performance-and-errors)

section_id: checklist

code:
from redis.backoff import ExponentialBackoff
from redis.retry import Retry

# Run 8 retries with exponential backoff strategy.
retry = Retry(ExponentialBackoff(), 8)

r = Redis(
  retry=retry,
    .
    .
)

section_id: retries

code:
# Only retry after a `TimeoutError`.
retry = Retry(ExponentialBackoff(), 3, supported_errors=(TimeoutError,))

code:
# Add `BusyLoadingError` to the default list of exceptions.
from redis.exceptions import (
   BusyLoadingError,
)
    .
    .

r = Redis(
  retry=retry,
  retry_on_error=[BusyLoadingError],
    .
    .
)

code:
# Issue a health check if the connection is idle for longer
# than three seconds.
r = Redis(
  health_check_interval = 3,
    .
    .
)

section_id: health-checks

code:
# Set a 15-second timeout for connections and a
# 5-second timeout for commands.
r = Redis(
  socket_connect_timeout=15,
  socket_timeout=5,
    .
    .
)

section_id: timeouts


title: Index and query documents

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

title: Initialize

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

Add the following dependencies. All of them are applicable to both JSON and hash,
except for the `Path` class, which is specific to JSON (see
[Path]() for a description of the
JSON path syntax).

#### Code Examples

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

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Python

**Python:**

[code example]

title: Create data

text:
Create some test data to add to your database. The example data shown
below is compatible with both JSON and hash objects.

#### Code Examples

Foundational: Define sample user data structures for indexing and querying

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Python

**Python:**

[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, Python

**Python:**

[code example]

The example uses an index called `idx:users` for JSON documents and adds
some JSON documents with the `user:` key prefix. To avoid errors, first
delete any existing index or documents whose names that might
conflict with the example:

#### 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, Python

**Python:**

[code example]

Create an index for the JSON data. The code below specifies that 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, Python

**Python:**

[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, Python

**Python:**

[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, Python

**Python:**

[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, Python

**Python:**

[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, Python

**Python:**

[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 use `HASH` for the `IndexType`
when you create the index.

First delete any existing index or documents
whose names might conflict with the hash example:

#### 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, Python

**Python:**

[code example]

Create 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:

#### 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, Python

**Python:**

[code example]

You use [`hset()`]() to add the hash
documents instead of [`json().set()`](),
but the same flat `userX` dictionaries work equally well with either
hash or JSON:

#### 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, Python

**Python:**

[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
result `Document` object instead of in an enclosing `json` dictionary:

#### 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, Python

**Python:**

[code example]

title: More information

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

code:
import redis
from redis.commands.json.path import Path
import redis.commands.search.aggregation as aggregations
import redis.commands.search.reducers as reducers
from redis.commands.search.field import TextField, NumericField, TagField
from redis.commands.search.index_definition import IndexDefinition, IndexType
from redis.commands.search.query import Query
import redis.exceptions

section_id: initialize

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

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

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

section_id: create-data

code: r = redis.Redis(decode_responses=True)

section_id: add-the-index

code:
try:
    r.ft("idx:users").dropindex(True)
except redis.exceptions.ResponseError:
    pass

r.delete("user:1", "user:2", "user:3")

code:
schema = (
    TextField("$.name", as_name="name"),
    TagField("$.city", as_name="city"),
    NumericField("$.age", as_name="age")
)

indexCreated = r.ft("idx:users").create_index(
    schema,
    definition=IndexDefinition(
        prefix=["user:"], index_type=IndexType.JSON
    )
)

code:
user1Set = r.json().set("user:1", Path.root_path(), user1)
user2Set = r.json().set("user:2", Path.root_path(), user2)
user3Set = r.json().set("user:3", Path.root_path(), user3)

section_id: add-the-data

code:
findPaulResult = r.ft("idx:users").search(
    Query("Paul @age:[30 40]")
)

print(findPaulResult)
# >>> Result{1 total, docs: [Document {'id': 'user:3', ...

section_id: query-the-data

code:
citiesResult = r.ft("idx:users").search(
    Query("Paul").return_field("$.city", as_field="city")
).docs

print(citiesResult)
# >>> [Document {'id': 'user:1', 'payload': None, ...

code:
req = aggregations.AggregateRequest("*").group_by(
    '@city', reducers.count().alias('count')
)

aggResult = r.ft("idx:users").aggregate(req).rows
print(aggResult)
# >>> [['city', 'London', 'count', '1'], ['city', 'Tel Aviv', 'count', '2']]

code:
try:
    r.ft("hash-idx:users").dropindex(True)
except redis.exceptions.ResponseError:
    pass

r.delete("huser:1", "huser:2", "huser:3")

section_id: differences-with-hash-documents

code:
hashSchema = (
    TextField("name"),
    TagField("city"),
    NumericField("age")
)

hashIndexCreated = r.ft("hash-idx:users").create_index(
    hashSchema,
    definition=IndexDefinition(
        prefix=["huser:"], index_type=IndexType.HASH
    )
)

code:
huser1Set = r.hset("huser:1", mapping=user1)
huser2Set = r.hset("huser:2", mapping=user2)
huser3Set = r.hset("huser:3", mapping=user3)

code:
findPaulHashResult = r.ft("hash-idx:users").search(
    Query("Paul @age:[30 40]")
)

print(findPaulHashResult)
# >>> Result{1 total, docs: [Document {'id': 'huser:3',
# >>>   'payload': None, 'name': 'Paul Zamir', ...


title: Scan iteration

summary: Iterate through results from `SCAN`, `HSCAN`, etc.

title: Content

text:
Redis has a small family of related commands that retrieve
keys and, in some cases, their associated values:

-   [`SCAN`]() retrieves keys
    from the main Redis keyspace.
-   [`HSCAN`]() retrieves keys and optionally,
    their values from a
    [hash]() object.
-   [`SSCAN`]() retrieves keys from a
    [set]() object.
-   [`ZSCAN`]() retrieves keys and their score values from a
    [sorted set]() object.

These commands can potentially return large numbers of results, so Redis
provides a paging mechanism to access the results in small, separate batches.
With the basic commands, you must maintain a cursor value in your code
to keep track of the current page. As a convenient alternative, `redis-py`
also lets you access the results using an
[iterator](https://docs.python.org/3/glossary.html#term-iterable).
This handles the paging transparently, so you simply need to process
the items it returns one-by-one in a `for` loop or pass the iterator
object itself in place of a
[sequence](https://docs.python.org/3/glossary.html#term-sequence).

Each of the commands has its own equivalent iterator. The following example shows
how to use a `SCAN` iterator on the Redis keyspace. Note that, as with the `SCAN`
command, the results are not sorted into any particular order, . Also, you
can pass `match`, `count`, and `_type` parameters to `scan_iter()` to constrain
the set of keys it returns (see the [`SCAN`]()
command page for examples).

[code example]

The iterators for the other commands are also named with `_iter()` after
the name of the basic command (`hscan_iter()`, `sscan_iter()`, and `zscan_iter()`).
They work in a similar way to `scan_iter()` except that you must pass a
key to identify the object you want to scan. The example below shows how to
iterate through the items in a sorted set using `zscan_iter()`.

[code example]

Note that in this case, the item returned by the iterator is a
[tuple](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences)
with two elements for the key and score. By default, `hscan_iter()`
also returns a 2-tuple for the key and value, but you can
pass a value of `True` for the `no_values` parameter to retrieve just
the keys:

[code example]

code:
import redis

r = redis.Redis(decode_responses=True)

r.set("key:1", "a")
r.set("key:2", "b")
r.set("key:3", "c")
r.set("key:4", "d")
r.set("key:5", "e")

for key in r.scan_iter():
    print(f"Key: {key}, value: {r.get(key)}")
# >>> Key: key:1, value: a
# >>> Key: key:4, value: d
# >>> Key: key:3, value: c
# >>> Key: key:2, value: b
# >>> Key: key:5, value: e

section_id: content

code:
r.zadd("battles", mapping={
    "hastings": 1066,
    "agincourt": 1415,
    "trafalgar": 1805,
    "somme": 1916,
})

for item in r.zscan_iter("battles"):
    print(f"Key: {item[0]}, value: {int(item[1])}")
# >>> Key: hastings, value: 1066
# >>> Key: agincourt, value: 1415
# >>> Key: trafalgar, value: 1805
# >>> Key: somme, value: 1916

code:
r.hset("details", mapping={
    "name": "Mr Benn",
    "address": "52 Festive Road",
    "hobbies": "Cosplay"
})

for key in r.hscan_iter("details", no_values=True):
    print(f"Key: {key}, value: {r.hget("details", key)}")
# >>> Key: name, value: Mr Benn
# >>> Key: address, value: 52 Festive Road
# >>> Key: hobbies, value: Cosplay


title: Pipelines and transactions

summary: Learn how to use Redis 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 standard
command methods (for example, `set()` and `get()`). The commands are
buffered in the pipeline and only execute when you call the `execute()`
method on the pipeline object. This method returns a list that contains
the results from all the commands in order.

Note that the command methods for a pipeline always return the original
pipeline object, so you can "chain" several commands together, as the
example below shows:

#### 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 pipeline actually executes as a transaction by default (that is to say,
all commands are executed in an uninterrupted sequence). However, if you
need to switch this behavior off, you can set the `transaction` parameter
to `False` when you create the pipeline:

[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 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 example below shows how to repeatedly attempt a transaction with a watched
key until it succeeds. The code reads a string
that represents a `PATH` variable for a command shell, then appends a new
command path to the string before attempting to write it back. If the watched
key is modified by another client before writing, the transaction aborts
with a `WatchError` exception, and the loop executes again for another attempt.
Otherwise, the loop terminates successfully.

#### 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]

Because this is a common pattern, the library includes a convenience
method called `transaction()` that handles the code to watch keys,
execute the transaction, and retry if necessary. Pass
`transaction()` a function that implements your main transaction code,
and also pass the keys you want to watch. The example below implements
the same basic transaction as the previous example but this time
using `transaction()`. Note that `transaction()` can't add the `multi()`
call automatically, so you must still place this correctly in your
transaction function.

#### 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]

section_id: execute-a-pipeline

code:
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']

code: pipe = r.pipeline(transaction=False)

section_id: execute-a-transaction

section_id: watch-keys-for-changes

code:
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/'

code:
using NRedisStack;
using StackExchange.Redis;
public class PipeTransExample
{
    public void Run()
    {
        var muxer = ConnectionMultiplexer.Connect("localhost:6379");
        var db = muxer.GetDatabase();

        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

        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

        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

        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
    }
}

code:
package example_commands_test

import (
	"context"
	"fmt"

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

func ExampleClient_transactions() {
	ctx := context.Background()

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

	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

	var pd0Result *redis.StatusCmd
	var pd3Result *redis.StatusCmd
	var pd4Result *redis.StatusCmd

	cmds, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
		pd0Result = (*redis.StatusCmd)(pipe.Get(ctx, "seat:0"))
		pd3Result = (*redis.StatusCmd)(pipe.Get(ctx, "seat:3"))
		pd4Result = (*redis.StatusCmd)(pipe.Get(ctx, "seat:4"))
		return nil
	})

	if err != nil {
		panic(err)
	}

	// The results are available only after the pipeline
	// has finished executing.
	fmt.Println(pd0Result.Val()) // >>> #0
	fmt.Println(pd3Result.Val()) // >>> #3
	fmt.Println(pd4Result.Val()) // >>> #4

	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

	var tx1Result *redis.IntCmd
	var tx2Result *redis.IntCmd
	var tx3Result *redis.IntCmd

	cmds, err = rdb.TxPipelined(ctx, func(trans redis.Pipeliner) error {
		tx1Result = trans.IncrBy(ctx, "counter:1", 1)
		tx2Result = trans.IncrBy(ctx, "counter:2", 2)
		tx3Result = trans.IncrBy(ctx, "counter:3", 3)
		return nil
	})

	if err != nil {
		panic(err)
	}

	fmt.Println(tx1Result.Val()) // >>> 2
	fmt.Println(tx2Result.Val()) // >>> 4
	fmt.Println(tx3Result.Val()) // >>> 6

	// 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:
import java.util.List;

import redis.clients.jedis.RedisClient;
import redis.clients.jedis.AbstractPipeline;
import redis.clients.jedis.AbstractTransaction;
import redis.clients.jedis.Response;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class PipeTransExample {

    public void run() {
        RedisClient jedis = RedisClient.create("redis://localhost:6379");

        // 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

        }

        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

        // 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/
            }
        }

        jedis.close();
    }
}

code:
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:
mod pipe_trans_tests {
    use redis::Commands;

    fn run() {
        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;
            }
        };

        for i in 0..8 {
            let key = format!("seat:{}", i);
            let _: () = r.del(&key).expect("Failed to delete key");
        }

        for i in 1..4 {
            let key = format!("counter:{}", i);
            let _: () = r.del(&key).expect("Failed to delete key");
        }

        // 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

        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;
            }
        };

        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;
            }
        };
    }
}