title: Predis guide (PHP)

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

title: Install

role: setup

text:
Use [Composer](https://getcomposer.org/) to install the `Predis` library
with the following command line:

[code example]

title: Connect and test

text:
Connect to a locally-running server on the standard port (6379)
with the following code:

#### 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 simple string to test the connection:

#### 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 [hash]()
object:

#### 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: More information

text:
The [Predis wiki on Github](https://github.com/predis/predis/wiki) has
information about the different connection options you can use.

See also the pages in this section for more information and examples:

code: composer require predis/predis

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:
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:
import { createClient } from 'redis';

const client = createClient();

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

await client.connect();

code:
import { Redis } from 'ioredis';

const redis = new Redis();

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:
await client.set('key', 'value');
const value = await client.get('key');
console.log(value); // >>> value

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

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:
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"
}
 */

code:
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"
}
 */

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'

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


title: Index and query documents

page_type: content

title: Initialize

role: content

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

Add the following dependencies:

#### Code Examples

Foundational: Import required dependencies for Redis Search and JSON operations

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[code example]

title: Create data

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

#### Code Examples

Foundational: Create sample JSON data structures for indexing and querying

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[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: Connect to a Redis server and establish a client connection

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[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 schema and prefix filtering

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[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 with indexed key prefixes for automatic indexing

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[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 data: Execute full-text and range queries on indexed JSON documents using FT.SEARCH

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[code example]

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

#### Code Examples

Restrict query results: Use query options to return specific fields from search results

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[code example]

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

#### Code Examples

Aggregation: Use aggregation queries to group and count results from indexed documents

**Difficulty:** Advanced

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[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 `On()` option
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.

#### Code Examples

Foundational: Create a search index for hash documents with HASH type specification

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[code example]

You use [`hmset()`]() to add the hash
documents instead of [`jsonset()`]().
Supply the fields as an array directly, without using
[`json_encode()`](https://www.php.net/manual/en/function.json-encode.php).

#### Code Examples

Foundational: Store hash documents using HSET for indexed retrieval

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[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 array rather than in a JSON string with `$` as its key:

#### Code Examples

Query data: Query hash documents using the same search syntax as JSON with different result formatting

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, PHP

**PHP:**

[code example]

title: More information

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

code:
<?php

require 'vendor/autoload.php';

use Predis\Client as PredisClient;

use Predis\Command\Argument\Search\AggregateArguments;
use Predis\Command\Argument\Search\CreateArguments;
use Predis\Command\Argument\Search\SearchArguments;
use Predis\Command\Argument\Search\SchemaFields\NumericField;
use Predis\Command\Argument\Search\SchemaFields\TextField;
use Predis\Command\Argument\Search\SchemaFields\TagField;

section_id: initialize

code:
$user1 = json_encode([
            'name' => 'Paul John',
            'email' => 'paul.john@example.com',
            'age' => 42,
            'city' => 'London',
        ], JSON_THROW_ON_ERROR);

        $user2 = json_encode([
            'name' => 'Eden Zamir',
            'email' => 'eden.zamir@example.com',
            'age' => 29,
            'city' => 'Tel Aviv',
        ], JSON_THROW_ON_ERROR);

        $user3 = json_encode([
            'name' => 'Paul Zamir',
            'email' => 'paul.zamir@example.com',
            'age' => 35,
            'city' => 'Tel Aviv',
        ], JSON_THROW_ON_ERROR);

section_id: create-data

section_id: add-the-index

code:
$schema = [
            new TextField('$.name', 'name'),
            new TagField('$.city', 'city'),
            new NumericField('$.age', "age"),
        ];

        try {
        $r->ftCreate("idx:users", $schema,
            (new CreateArguments())
                ->on('JSON')
                ->prefix(["user:"]));
        }
        catch (Exception $e) {
            echo $e->getMessage(), PHP_EOL;
        }

section_id: add-the-data

code:
$res = $r->ftSearch("idx:users", "Paul @age:[30 40]");
        echo json_encode($res), PHP_EOL;
        // >>> [1,"user:3",["$","{\"name\":\"Paul Zamir\",\"email\":\"paul.zamir@example.com\",\"age\":35,\"city\":\"London\"}"]]

section_id: query-the-data

code:
$arguments = new SearchArguments();
        $arguments->addReturn(3, '$.city', true, 'thecity');
        $arguments->dialect(2);
        $arguments->limit(0, 5);

        $res = $r->ftSearch("idx:users", "Paul", $arguments);

        echo json_encode($res), PHP_EOL;
        // >>> [2,"user:1",["thecity","London"],"user:3",["thecity","Tel Aviv"]]

code:
$ftAggregateArguments = (new AggregateArguments())
            ->groupBy('@city')
            ->reduce('COUNT', true, 'count');

        $res = $r->ftAggregate('idx:users', '*', $ftAggregateArguments);
        echo json_encode($res), PHP_EOL;
        // >>> [2,["city","London","count","1"],["city","Tel Aviv","count","2"]]

code:
$hashSchema = [
            new TextField('name'),
            new TagField('city'),
            new NumericField('age'),
        ];

        try {
        $r->ftCreate("hash-idx:users", $hashSchema,
            (new CreateArguments())
                ->on('HASH')
                ->prefix(["huser:"]));
        }
        catch (Exception $e) {
            echo $e->getMessage(), PHP_EOL;
        }

section_id: differences-with-hash-documents

code:
$r->hmset('huser:1', [
            'name' => 'Paul John',
            'email' => 'paul.john@example.com',
            'age' => 42,
            'city' => 'London',
        ]);

        $r->hmset('huser:2', [
            'name' => 'Eden Zamir',
            'email' => 'eden.zamir@example.com',
            'age' => 29,
            'city' => 'Tel Aviv',
        ]);

        $r->hmset('huser:3', [
            'name' => 'Paul Zamir',
            'email' => 'paul.zamir@example.com',
            'age' => 35,
            'city' => 'Tel Aviv',
        ]);

code:
$res = $r->ftSearch("hash-idx:users", "Paul @age:[30 40]");
        echo json_encode($res), PHP_EOL;
        // >>> [1,"huser:3",["age","35","city","Tel Aviv","email","paul.zamir@example.com","name","Paul Zamir"]]


title: Index and query vectors

title: Overview

role: overview

text:
[Redis Search]()
lets you index vector fields in [hash]()
or [JSON]() objects (see the
[Vectors]()
reference page for more information).
Among other things, vector fields can store *text embeddings*, which are AI-generated vector
representations of the semantic information in pieces of text. The
[vector distance]()
between two embeddings indicates how similar they are semantically. By comparing the
similarity of an embedding generated from some query text with embeddings stored in hash
or JSON fields, Redis can retrieve documents that closely match the query in terms
of their meaning.

The example below uses the [HuggingFace](https://huggingface.co/) model
[`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)
to generate the vector embeddings to store and index with Redis Search.
The code is first demonstrated for hash documents with a
separate section to explain the
[differences with JSON documents](#differences-with-json-documents).

From [v3.0.0](https://github.com/predis/predis/releases/tag/v3.0.0) onwards,
`Predis` uses query dialect 2 by default.
Redis Search methods such as [`ftSearch()`]()
will explicitly request this dialect, overriding the default set for the server.
See
[Query dialects]()
for more information.

title: Initialize

text:
You can use the [TransformersPHP](https://transformers.codewithkyrian.com/)
library to create the vector embeddings. Install the library with the following
command:

[code example]

title: Import dependencies

text:
Import the following classes and function in your source file:

[code example]

title: Create a tokenizer instance

text:
The code below shows how to use the
[`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)
tokenizer to generate the embeddings. The vectors that represent the
embeddings have 384 dimensions, regardless of the length of the input
text. Here, the `pipeline()` call creates the `$extractor` function that
generates embeddings from text:

[code example]

title: Create the index

text:
Connect to Redis and delete any index previously created with the
name `vector_idx`. (The
[`ftdropindex()`]()
call throws an exception if the index doesn't already exist, which is
why you need the `try...catch` block.)

[code example]

Next, create the index.
The schema in the example below includes three fields: the text content to index, a
[tag]()
field to represent the "genre" of the text, and the embedding vector generated from
the original text content. The `embedding` field specifies
[HNSW]()
indexing, the
[L2]()
vector distance metric, `Float32` values to represent the vector's components,
and 384 dimensions, as required by the `all-MiniLM-L6-v2` embedding model.

The `CreateArguments` parameter to [`ftcreate()`]()
specifies hash objects for storage and a prefix `doc:` that identifies the hash objects
to index.

[code example]

title: Add data

text:
You can now supply the data objects, which will be indexed automatically
when you add them with [`hmset()`](), as long as
you use the `doc:` prefix specified in the index definition.

Use the `$extractor()` function as shown below to create the embedding that
represents the `content` field. Note that `$extractor()` can generate multiple
embeddings from multiple strings parameters at once, so it returns an array of
embedding vectors. Here, there is only one embedding in the returned array.
The `normalize:` and `pooling:` named parameters relate to details
of the embedding model (see the
[`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)
page for more information).

To add an embedding as a field of a hash object, you must encode the
vector array as a binary string. The built-in
[`pack()`](https://www.php.net/manual/en/function.pack.php) function is a convenient
way to do this in PHP, using the `g*` format specifier to denote a packed
array of `float` values. Note that if you are using
[JSON]()
objects to store your documents instead of hashes, then you should store
the `float` array directly without first converting it to a binary
string (see [Differences with JSON documents](#differences-with-json-documents)
below).

[code example]

title: Run a query

text:
After you have created the index and added the data, you are ready to run a query.
To do this, you must create another embedding vector from your chosen query
text. Redis calculates the vector distance between the query vector and each
embedding vector in the index as it runs the query. You can request the results to be
sorted to rank them in order of ascending distance.

The code below creates the query embedding using the `$extractor()` function, as with
the indexing, and passes it as a parameter when the query executes (see
[Vector search]()
for more information about using query parameters with embeddings).
The query is a
[K nearest neighbors (KNN)]()
search that sorts the results in order of vector distance from the query vector.

The results are returned as an array with the number of results in the
first element. The remaining elements are alternating pairs with the
key of the returned document (for example, `doc:0`) first, followed by an array containing
the fields you requested (again as alternating key-value pairs).

[code example]

Assuming you have added the code from the steps above to your source file,
it is now ready to run, but note that it may take a while to complete when
you run it for the first time (which happens because the tokenizer must download the
`all-MiniLM-L6-v2` model data before it can
generate the embeddings). When you run the code, it outputs the following result text:

[code example]

Note that the results are ordered according to the value of the `distance`
field, with the lowest distance indicating the greatest similarity to the query.
As you would expect, the text *"That is a very happy person"* (from the `doc:0`
document)
is the result judged to be most similar in meaning to the query text
*"That is a happy person"*.

title: Differences with JSON documents

text:
Indexing JSON documents is similar to hash indexing, but there are some
important differences. JSON allows much richer data modeling with nested fields, so
you must supply a [path]() in the schema
to identify each field you want to index. However, you can declare a short alias for each
of these paths to avoid typing it in full for
every query. Also, you must specify `JSON` with the `on()` option when you create the index.

The code below shows these differences, but the index is otherwise very similar to
the one created previously for hashes:

[code example]

Use [`jsonset()`]() to add the data
instead of [`hmset()`](). The arrays
that specify the fields have roughly the same structure as the ones used for
`hmset()` but you should use the standard library function
[`json_encode()`](https://www.php.net/manual/en/function.json-encode.php)
to generate a JSON string representation of the array.

An important difference with JSON indexing is that the vectors are
specified using arrays instead of binary strings. Simply add the
embedding as an array field without using the `pack()` function as you
would with a hash.

[code example]

The query is almost identical to the one for the hash documents. This
demonstrates how the right choice of aliases for the JSON paths can
save you having to write complex queries. An important thing to notice
is that the vector parameter for the query is still specified as a
binary string (using the `pack()` function), even though the data for
the `embedding` field of the JSON was specified as an array.

[code example]

Apart from the `jdoc:` prefixes for the keys, the result from the JSON
query is the same as for hash:

[code example]

title: Learn more

role: related

text:
See
[Vector search]()
for more information about the indexing options, distance metrics, and query format
for vectors.

code:
<?php

require 'vendor/autoload.php';

// TransformersPHP
use function Codewithkyrian\Transformers\Pipelines\pipeline;

// Redis client and Redis Search classes.
use Predis\Client;
use Predis\Command\Argument\Search\CreateArguments;
use Predis\Command\Argument\Search\SearchArguments;
use Predis\Command\Argument\Search\SchemaFields\TextField;
use Predis\Command\Argument\Search\SchemaFields\TagField;
use Predis\Command\Argument\Search\SchemaFields\VectorField;

section_id: import-dependencies

code: $extractor = pipeline('embeddings', 'Xenova/all-MiniLM-L6-v2');

section_id: create-a-tokenizer-instance

code:
$client = new Predis\Client([
    'host' => 'localhost',
    'port' => 6379,
]);

try {
    $client->ftdropindex("vector_idx");
} catch (Exception $e){}

section_id: create-the-index

code:
$schema = [
    new TextField("content"),
    new TagField("genre"),
    new VectorField(
        "embedding",
        "HNSW",
        [
            "TYPE", "FLOAT32",
            "DIM", 384,
            "DISTANCE_METRIC", "L2"
        ]
    )
];

$client->ftcreate("vector_idx", $schema,
    (new CreateArguments())
        ->on('HASH')
        ->prefix(["doc:"])
);

code:
$content = "That is a very happy person";
$emb = $extractor($content, normalize: true, pooling: 'mean');

$client->hmset("doc:0",[
    "content" => $content,
    "genre" => "persons",
    "embedding" => pack('g*', ...$emb[0])
]);

$content = "That is a happy dog";
$emb = $extractor($content, normalize: true, pooling: 'mean');

$client->hmset("doc:1",[
    "content" => $content,
    "genre" => "pets",
    "embedding" => pack('g*', ...$emb[0])
]);

$content = "Today is a sunny day";
$emb = $extractor($content, normalize: true, pooling: 'mean');

$client->hmset("doc:2",[
    "content" => $content,
    "genre" => "weather",
    "embedding" => pack('g*', ...$emb[0])
]);

section_id: add-data

code:
$queryText = "That is a happy person";
$queryEmb = $extractor($queryText, normalize: true, pooling: 'mean');

$result = $client->ftsearch(
    "vector_idx",
    '*=>[KNN 3 @embedding $vec AS vector_distance]',
    new SearchArguments()
        ->addReturn(1, "vector_distance")
        ->dialect("2")
        ->params([
            "vec", pack('g*', ...$queryEmb[0])
        ])
        ->sortBy("vector_distance")
);

$numResults = $result[0];
echo "Number of results: $numResults" . PHP_EOL;
// >>> Number of results: 3

for ($i = 1; $i < ($numResults * 2 + 1); $i += 2) {
    $key = $result[$i];
    echo "Key: $key" . PHP_EOL;
    $fields = $result[$i + 1];
    echo "Field: {$fields[0]}, Value: {$fields[1]}" . PHP_EOL;
}
// >>> Key: doc:0
// >>> Field: vector_distance, Value: 3.76152896881
// >>> Key: doc:1
// >>> Field: vector_distance, Value: 18.6544265747
// >>> Key: doc:2
// >>> Field: vector_distance, Value: 44.6189727783

section_id: run-a-query

code:
Number of results: 3
Key: doc:0
Field: vector_distance, Value: 3.76152896881
Key: doc:1
Field: vector_distance, Value: 18.6544265747
Key: doc:2
Field: vector_distance, Value: 44.6189727783

code:
$jsonSchema = [
    new TextField("$.content", "content"),
    new TagField("$.genre", "genre"),
    new VectorField(
        "$.embedding",
        "HNSW",
        [
            "TYPE", "FLOAT32",
            "DIM", 384,
            "DISTANCE_METRIC", "L2"
        ],
        "embedding",
    )
];

$client->ftcreate("vector_json_idx", $jsonSchema,
    (new CreateArguments())
        ->on('JSON')
        ->prefix(["jdoc:"])
);

section_id: differences-with-json-documents

code:
$content = "That is a very happy person";
$emb = $extractor($content, normalize: true, pooling: 'mean');

$client->jsonset("jdoc:0", "$",
    json_encode(
        [
            "content" => $content,
            "genre" => "persons",
            "embedding" => $emb[0]
        ],
        JSON_THROW_ON_ERROR
    )
);

$content = "That is a happy dog";
$emb = $extractor($content, normalize: true, pooling: 'mean');

$client->jsonset("jdoc:1","$",
    json_encode(
        [
            "content" => $content,
            "genre" => "pets",
            "embedding" => $emb[0]
        ],
        JSON_THROW_ON_ERROR
    )
);

$content = "Today is a sunny day";
$emb = $extractor($content, normalize: true, pooling: 'mean');

$client->jsonset("jdoc:2", "$",
    json_encode(
        [
            "content" => $content,
            "genre" => "weather",
            "embedding" => $emb[0]
        ],
        JSON_THROW_ON_ERROR
    )
);

code:
$queryText = "That is a happy person";
$queryEmb = $extractor($queryText, normalize: true, pooling: 'mean');

$result = $client->ftsearch(
    "vector_json_idx",
    '*=>[KNN 3 @embedding $vec AS vector_distance]',
    new SearchArguments()
        ->addReturn(1, "vector_distance")
        ->dialect("2")
        ->params([
            "vec", pack('g*', ...$queryEmb[0])
        ])
        ->sortBy("vector_distance")
);

code:
Number of results: 3
Key: jdoc:0
Field: vector_distance, Value: 3.76152896881
Key: jdoc:1
Field: vector_distance, Value: 18.6544265747
Key: jdoc:2
Field: vector_distance, Value: 44.6189727783


title: Connection pools and multiplexing

summary: Manage Redis connections efficiently

title: Overview

text:
Redis example code generally opens a connection, demonstrates
a command or feature, and then closes. Real-world code typically
has short bursts of communication with the server and periods of
inactivity in between. Opening and closing connections
involves some overhead and leads to inefficiency if you do
it frequently. This means that you can improve the performance of production
code by making as few separate connections as possible.

Managing connections in your own code can be tricky, so the Redis
client libraries give you some help. The two basic approaches to
connection management are called *connection pooling* and *multiplexing*.
The [`redis-py`](),
[`jedis`](), and
[`go-redis`]() clients support
connection pooling, while
[`NRedisStack`]()
supports multiplexing.
[`Lettuce`]()
supports both approaches.

title: Connection pooling

text:
When you initialize a connection pool, the client opens a small number
of connections and adds them to the pool.

Each time you "open" a connection
from the pool, the client returns one of these existing
connections and notes the fact that it is in use.

When you later "close"
the connection, the client puts it back into the pool of available
connections without actually closing it.

If all connections in the pool are in use but the app needs more, then
the client can simply open new connections as necessary. In this way, the client
eventually finds the right number of connections to satisfy your
app's demands.

title: Multiplexing

text:
Instead of pooling several connections, a multiplexer keeps a
single connection open and uses it for all traffic between the
client and the server. The "connections" returned to your code are
used to identify where to send the response data from your commands.

Note that it is not a problem if the multiplexer receives several commands close
together in time. When this happens, the multiplexer can often combine the commands into a
[pipeline](), which
improves efficiency.

Multiplexing offers high efficiency but works transparently without requiring
any special code to enable it in your app. The main disadvantage of multiplexing compared to
connection pooling is that it can't support the blocking "pop" commands (such as
[`BLPOP`]()) since these would stall the
connection for all callers.


title: Connect to Azure Managed Redis

summary: Learn how to authenticate to an Azure Managed Redis (AMR) database

title: Overview

text:
The [`redis-entra-id`](https://github.com/redis/redis-py-entraid) package
lets you authenticate your app to
[Azure Managed Redis (AMR)](https://azure.microsoft.com/en-us/products/managed-redis)
using [Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/).
You can authenticate using a system-assigned or user-assigned
[managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview)
or a [service principal](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals),
letting `redis-entra-id` fetch and renew the authentication tokens for you automatically.

title: Install

text:
Install [`redis-py`]() first,
if you have not already done so. Then, install `redis-entra-id` with the
following command:

[code example]

title: Create a `CredentialProvider` instance

text:
A `CredentialProvider` object obtains the authentication credentials you
need when you connect to Redis. See the sections below to learn how
to create the `CredentialProvider` instances for AMR
using the factory functions that `redis-entra-id` provides.

title: `CredentialProvider` for a service principal

text:
Use the `create_from_service_principal()` factory function to create a
`CredentialProvider` that authenticates to AMR using a
service principal (see the
[Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals) to learn more about service principals).

You will need the following details of your service principal to make the connection:

- Client ID
- Client secret
- Tenant ID

The example below shows how to import the required modules and call
`create_from_service_principal()`:

[code example]

This uses a default configuration but you can also provide a custom
configuration using the `token_manager_config` parameter:

[code example]

title: `CredentialProvider` for a managed identity

text:
Use the `create_from_managed_identity()` factory function to create a
`CredentialProvider` that authenticates to AMR using a
managed identity (see the
[Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) to learn more about managed identities).

The example below shows how to import the required modules and call
`create_from_managed_identity()`.
Pass `ManagedIdentityType.USER_ASSIGNED` or `ManagedIdentityType.SYSTEM_ASSIGNED`
as the `identity_type` parameter.

[code example]

This uses a default configuration but you can also provide a custom
configuration using the `token_manager_config` parameter:

[code example]

title: Connect

text:
When you have created your `CredentialProvider` instance, you are ready to
connect to AMR.
The example below shows how to pass the instance as a parameter to the standard
`RedisCluster()` connection method.
 Azure requires you to use
[Transport Layer Security (TLS)](https://en.wikipedia.org/wiki/Transport_Layer_Security)
when you connect (see
[Connect with TLS]() for more information).

[code example]

code: pip install redis-entraid

code:
from redis_entraid.cred_provider import *

credential_provider = create_from_service_principal(
    <CLIENT_ID>,
    <CLIENT_SECRET>,
    <TENANT_ID>
)

code:
credential_provider = create_from_service_principal(
    <CLIENT_ID>,
    <CLIENT_SECRET>,
    <TENANT_ID>,
    token_manager_config=TokenManagerConfig(
        expiration_refresh_ratio=0.9,
        lower_refresh_bound_millis=DEFAULT_LOWER_REFRESH_BOUND_MILLIS,
        token_request_execution_timeout_in_ms=DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_IN_MS,
        retry_policy=RetryPolicy(
            max_attempts=5,
            delay_in_ms=50
        )
    )
)

code:
from redis_entraid.cred_provider import *

credential_provider = create_from_managed_identity(
    identity_type=ManagedIdentityType.SYSTEM_ASSIGNED,
)

code:
credential_provider = create_from_managed_identity(
    identity_type=ManagedIdentityType.SYSTEM_ASSIGNED,
    ...

    token_manager_config=TokenManagerConfig(
        expiration_refresh_ratio=0.9,
        lower_refresh_bound_millis=DEFAULT_LOWER_REFRESH_BOUND_MILLIS,
        token_request_execution_timeout_in_ms=DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_IN_MS,
        retry_policy=RetryPolicy(
            max_attempts=5,
            delay_in_ms=50
        )
    )
)

code:
from redis import RedisCluster
# ...

# AMR databases have clustering enabled by default.
r = RedisCluster(
    host=<HOST>, port=<PORT>,
    credential_provider=credential_provider,
    ssl=True,
    ssl_certfile="./redis_user.crt",
    ssl_keyfile="./redis_user_private.key",
    ssl_ca_certs="./redis_ca.pem"
)

// Test the connection.
print("The database size is: {}".format(client.dbsize()))

section_id: connect


title: Connect to the server

summary: Connect your Python application to a Redis database

title: Basic connection

text:
Connect to localhost on port 6379, set a value in Redis, and retrieve it. All responses are returned as bytes in Python. To receive decoded strings, set `decode_responses=True`. For more connection options, see [these examples](https://redis.readthedocs.io/en/stable/examples.html).

[code example]

Store and retrieve a simple string.

[code example]

Store and retrieve a dict.

[code example]

title: Connect to a Redis cluster

text:
To connect to a Redis cluster, use `RedisCluster`.

[code example]
For more information, see [redis-py Clustering](https://redis.readthedocs.io/en/stable/clustering.html).

title: Connect to your production Redis with TLS

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

[code example]
For more information, see [redis-py TLS examples](https://redis.readthedocs.io/en/stable/examples/ssl_connection_examples.html).

title: Connect using client-side caching

text:
Client-side caching is a technique to reduce network traffic between
the client and server, resulting in better performance. See
[Client-side caching introduction]()
for more information about how client-side caching works and how to use it effectively.

To enable client-side caching, add some extra parameters when you connect
to the server:

-   `protocol`: (Required) You must pass a value of `3` here because
    client-side caching requires the [RESP3]()
    protocol.
-   `cache_config`: (Required) Pass `cache_config=CacheConfig()` here to enable client-side caching.

The example below shows the simplest client-side caching connection to the default host and port,
`localhost:6379`.
All of the connection variants described above accept these parameters, so you can
use client-side caching with a connection pool or a cluster connection in exactly the same way.

Client-side caching requires redis-py v5.1.0 or later.
To maximize compatibility with all Redis products, client-side caching
is supported by Redis v7.4 or later.

The [Redis server products]() support
[opt-in/opt-out]() mode
and [broadcasting mode]()
for CSC, but these modes are not currently implemented by `redis-py`.

[code example]

You can see the cache working if you connect to the same Redis database
with [`redis-cli`]() and run the
[`MONITOR`]() command. If you run the
code above with the `cache_config` line commented out, you should see
the following in the CLI among the output from `MONITOR`:

[code example]

The server responds to both `get("city")` calls.
If you run the code again with `cache_config` uncommented, you will see

[code example]

The first `get("city")` call contacted the server but the second
call was satisfied by the cache.

title: Removing items from the cache

text:
You can remove individual keys from the cache with the
`delete_by_redis_keys()` method. This removes all cached items associated
with the keys, so all results from multi-key commands (such as
[`MGET`]()) and composite data structures
(such as [hashes]()) will be
cleared at once. The example below shows the effect of removing a single
key from the cache:

[code example]

You can also clear all cached items using the `flush()`
method:

[code example]

The client will also flush the cache automatically
if any connection (including one from a connection pool)
is disconnected.

title: Connect with a connection pool

text:
For production usage, you should use a connection pool to manage
connections rather than opening and closing connections individually.
A connection pool maintains several open connections and reuses them
efficiently. When you open a connection from a pool, the pool allocates
one of its open connections. When you subsequently close the same connection,
it is not actually closed but simply returned to the pool for reuse.
This avoids the overhead of repeated connecting and disconnecting.
See
[Connection pools and multiplexing]()
for more information.

Use the following code to connect with a connection pool:

[code example]

title: Retrying connections

text:
A connection will sometimes fail because of a transient problem, such as a
network outage or a server that is temporarily unavailable. In these cases,
retrying the connection after a short delay will usually succeed. `redis-py` uses
a simple retry strategy by default, but there are various ways you can customize
this behavior to suit your use case. See
[Retries]()
for more information about custom retry strategies, with example code.

title: Connect using Smart client handoffs (SCH)

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.

By default, `redis-py` always attempts to connect via SCH but falls back to
a non-SCH connection if the server doesn't support it. However, you can configure SCH
explicitly by passing a `MaintNotificationsConfig` object during the connection,
as shown in the following example:

[code example]

To disable SCH, pass `enabled=False` in the `MaintNotificationsConfig` object:

[code example]

SCH requires the [RESP3]()
protocol, so you must set `protocol=3` explicitly when you connect.

The `MaintNotificationsConfig` constructor accepts the following parameters:

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `enabled` | `bool` | `True` | Whether or not to enable SCH. |
| `proactive_reconnect` | `bool` | `True` | Whether or not to automatically reconnect when a node is replaced. |
| `endpoint_type` | `EndpointType` | Auto-detect | The type of endpoint to use for the connection. The options are `EndpointType.EXTERNAL_IP`, `EndpointType.INTERNAL_IP`, `EndpointType.EXTERNAL_FQDN`, `EndpointType.INTERNAL_FQDN`, and `EndpointType.NONE`. |
| `relaxed_timeout` | `int` | `20` | The timeout (in seconds) to use while the server is performing maintenance. A value of `-1` disables the relax timeout and just uses the normal timeout during maintenance. |

 Redis Cloud supports relaxed timeouts *only* (and not pre-handoffs) for SCH if you are using
either [AWS PrivateLink]() or
[Google Cloud Private Service Connect]()
(see [Smart client handoffs]() for more information).
To use relaxed timeouts with these services, you should set `endpoint_type=EndpointType.NONE`
when you connect. All other configurations have full support for both relaxed timeouts and pre-handoffs.

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

section_id: basic-connection

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

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

code:
from redis.cluster import RedisCluster

rc = RedisCluster(host='localhost', port=16379)

print(rc.get_nodes())
# [[host=127.0.0.1,port=16379,name=127.0.0.1:16379,server_type=primary,redis_connection=Redis<ConnectionPool<Connection<host=127.0.0.1,port=16379,db=0>>>], ...

rc.set('foo', 'bar')
# True

rc.get('foo')
# b'bar'

section_id: connect-to-a-redis-cluster

code:
import redis

r = redis.Redis(
    host="my-redis.cloud.redislabs.com", port=6379,
    username="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,
    ssl_certfile="./redis_user.crt",
    ssl_keyfile="./redis_user_private.key",
    ssl_ca_certs="./redis_ca.pem",
)
r.set('foo', 'bar')
# True

r.get('foo')
# b'bar'

code:
import redis
from redis.cache import CacheConfig

r = redis.Redis(
    protocol=3,
    cache_config=CacheConfig(),
    decode_responses=True
)

r.set("city", "New York")
cityNameAttempt1 = r.get("city")    # Retrieved from Redis server and cached
cityNameAttempt2 = r.get("city")    # Retrieved from cache

section_id: connect-using-client-side-caching

code:
r.hget("person:1", "name") # Read from the server
r.hget("person:1", "name") # Read from the cache

r.hget("person:2", "name") # Read from the server
r.hget("person:2", "name") # Read from the cache

cache = r.get_cache()
cache.delete_by_redis_keys(["person:1"])

r.hget("person:1", "name") # Read from the server
r.hget("person:1", "name") # Read from the cache

r.hget("person:2", "name") # Still read from the cache

section_id: removing-items-from-the-cache

code:
r.hget("person:1", "name") # Read from the server
r.hget("person:1", "name") # Read from the cache

r.hget("person:2", "name") # Read from the cache
r.hget("person:2", "name") # Read from the cache

cache = r.get_cache()
cache.flush()

r.hget("person:1", "name") # Read from the server
r.hget("person:1", "name") # Read from the cache

r.hget("person:2", "name") # Read from the server
r.hget("person:2", "name") # Read from the cache

code:
import redis

pool = redis.ConnectionPool().from_url("redis://localhost")
r1 = redis.Redis().from_pool(pool)
r2 = redis.Redis().from_pool(pool)
r3 = redis.Redis().from_pool(pool)

r1.set("wind:1", "Hurricane")
r2.set("wind:2", "Tornado")
r3.set("wind:3", "Mistral")

r1.close()
r2.close()
r3.close()

pool.close()

section_id: connect-with-a-connection-pool

code:
import redis
from redis.maint_notifications import MaintNotificationsConfig, EndpointType

r = redis.Redis(
    decode_responses=True,
    protocol=3,
    maint_notifications_config=MaintNotificationsConfig(
        proactive_reconnect=True,
        relaxed_timeout=10,
        endpoint_type=EndpointType.EXTERNAL_IP
    ),
    ...
)

section_id: connect-using-smart-client-handoffs-sch

code:
r = redis.Redis(
    maint_notifications_config=MaintNotificationsConfig(
        enabled=False,
    ),
    ...
)


title: Error handling

summary: Learn how to handle errors when using redis-py

title: Overview

text:
redis-py uses **exceptions** to signal errors. The redis-py documentation mainly
shows the "happy path" in code examples and omits error handling for brevity.
This page explains how
redis-py'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 hierarchy

role: errors

text:
redis-py organizes exceptions in a hierarchy. The base exception is `redis.RedisError`, with specific subclasses for different error types, as shown below:

[code example]

title: Key exceptions

text:
The following exceptions are the most commonly encountered in redis-py applications.
See
[Categories of errors]()
for a more detailed discussion of these errors and their causes.

| Exception | When it occurs | Recoverable | Recommended action |
|---|---|---|---|
| `redis.ConnectionError` | Network or connection issues | ✅ | Retry with backoff or fall back to alternative |
| `redis.TimeoutError` | Operation exceeded timeout | ✅ | Retry with backoff |
| `redis.ResponseError` | Invalid command or Redis error response | ❌ | Fix the command or arguments |
| `redis.DataError` | Data serialization/deserialization issues | ⚠ | Log, invalidate cache, fetch fresh data |

title: Applying error handling patterns

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

title: Pattern 1: Fail fast

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

[code example]

title: Pattern 2: Graceful degradation

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

[code example]

title: Pattern 3: Retry with backoff

text:
Retry on temporary errors like timeouts (see
[Pattern 3: Retry with backoff]()
for a full description). redis-py has built-in retry logic
which is highly configurable. You can customize the retry strategy
(or supply your own custom strategy) and you can also specify which errors
should be retried. See
[Production usage]()
for more information.

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 when you use `async`/`await`,
as shown in the example below:

[code example]

title: See also

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

code:
"RedisError":
    _meta:
        description: "Base class for all redis-py exceptions"
    "ConnectionError":
        "TimeoutError":
        "BusyLoadingError":
    "ResponseError":
    "InvalidResponse":
    "DataError":
    "PubSubError":
    "...":
        _meta:
            ellipsis: true
            description: "Other exception types"

section_id: exception-hierarchy

code:
import redis

r = redis.Redis()

try:
    result = r.get(key)
except redis.ResponseError:
    # This indicates a bug in the code
    raise

code:
try:
    cached_value = r.get(key)
    if cached_value:
        return cached_value
except redis.ConnectionError:
    logger.warning("Cache unavailable, using database")

# Fallback to database
return database.get(key)

code:
try:
    r.setex(key, 3600, value)
except redis.ConnectionError:
    logger.warning(f"Failed to cache {key}, continuing without cache")
    # Application continues normally

code:
import redis.asyncio as redis

async def get_with_fallback(key):
    r = await redis.from_url("redis://localhost")
    try:
        return await r.get(key)
    except redis.ConnectionError:
        logger.warning("Cache unavailable")
        return await database.get(key)
    finally:
        await r.close()

section_id: async-error-handling


title: Client-side geographic failover

summary: Improve reliability using the failover features of redis-py.

title: Overview

text:
redis-py supports [Client-side geographic failover](https://en.wikipedia.org/wiki/Failover)
to improve the availability of connections to Redis databases. This page explains
how to configure redis-py for failover. For an overview of the concepts,
see the main [Client-side geographic failover]() page.

title: Failover configuration

text:
The example below shows a simple case with a list of two servers,
`redis-east` and `redis-west`, where `redis-east` is the preferred
target. If `redis-east` fails, redis-py should fail over to
`redis-west`.

Supply the weighted endpoints using a list of `DatabaseConfig` objects
(see [Selecting a failover target]() for a full description of how
the weighted list is used).
Use the `weight` option to order the endpoints, with the highest
weight being tried first. Then, use the list to create a `MultiDbConfig` object,
which you can pass to the `MultiDBClient` constructor to create the client.
`MultiDBClient` implements the usual Redis commands using an internal
`RedisClient` instance, but will also handle the connection management and failover transparently.

[code example]

title: Endpoint configuration

text:
The `DatabaseConfig` class provides several options to configure each endpoint, as
described in the table below. Supply the configurations for the whole set of
endpoints by passing a list of `DatabaseConfig` objects to the `MultiDbConfig`
constructor in the `databases_config` parameter.

| Option | Description |
| --- | --- |
| `client_kwargs` | Keyword parameters to pass to the internal client constructor for this endpoint. Use it to specify the host, port, username, password, and other connection parameters (see [Connect to the server]() for more information). This is especially useful if you are using a custom client class (see [Client configuration](#client-configuration) below for more information). |
| `from_url` | Redis URL to connect to this endpoint, as an alternative to passing the host and port in `client_kwargs`. |
| `from_pool` | A `ConnectionPool` to supply the endpoint connection (see [Connect with a connection pool]() for more information) |
| `weight` | Priority of the endpoint, with higher values being tried first. Default is `1.0`. |
| `grace_period` | Duration in seconds to keep an unhealthy endpoint disabled before attempting a failback. Default is `60` seconds. |
| `health_check_url` | URL for health checks that use the database's REST API (see [`LagAwareHealthCheck`](#lag-aware-health-check) for more information). |

title: Client configuration

text:
`MultiDbConfig` provides the `client_class` option to specify the class of the internal client to use for each endpoint. The default is the basic `redis.Redis` client, but
you could, for example, replace this with `redis.asyncio.client.Redis` for an asynchronous basic client, or with `redis.cluster.RedisCluster`/`redis.asyncio.cluster.RedisCluster` for a cluster client. Use the `client_kwargs` option of `DatabaseConfig` to supply any extra parameters required by the client class (see [Endpoint configuration](#endpoint-configuration) above for more information).

[code example]

title: Circuit breaker configuration

text:
`MultiDbConfig` gives you several options to configure the circuit breaker
(see [Detecting connection problems]() for more information on how the
circuit breaker works):

| Option | Description |
| --- | --- |
| `failures_detection_window` | Duration in seconds to keep failures and successes in the sliding window. Default is `2` seconds. |
| `min_num_failures` | Minimum number of failures that must occur to trigger a failover. Default is `1000`. |
| `failure_rate_threshold` | Fraction of failed commands required to trigger a failover. Default is `0.1` (10%). |

title: Retry configuration

text:
`MultiDbConfig` provides the `command_retry` option to configure retries for failed commands. This follows the usual approach to configuring retries used with a standard
`RedisClient` connection (see [Retries]() for more information).

[code example]

title: Failback configuration

text:
The `MultiDbConfig` constructor provides the following option to configure the failback behavior:

| Option | Description |
| --- | --- |
| `auto_fallback_interval` | Time interval between automatic failback attempts. Set this to `-1` to disable automatic failback. Default value is `30` seconds (which means failback is enabled by default). |

title: General failover configuration

text:
There are also a few other options you can pass to the `MultiDbConfig` constructor to control the failover behavior:

| Option | Description |
| --- | --- |
| `failover_attempts` | Number of attempts to fail over to a new endpoint before giving up. Default is `10`. |
| `failover_delay` | Time interval between successive failover attempts. Default is `12` seconds. |
| `event_dispatcher` | `EventDispatcher` object to use for emitting events. Supply this to register custom event listeners (see [Failover callbacks](#failover-callbacks) below for more information). |

title: Failover callbacks

text:
You may want to take some custom action when a failover occurs. For example, you could log a warning, increment a metric, or externally persist the cluster connection state.

You can implement a custom event listener using a class that implements `EventListenerInterface`. This includes a `listen()` method that is called when
the event occurs. You should specifically listen for the `ActiveDatabaseChanged`
event, which is emitted when a failover happens.

The example below shows how to implement a simple listener class and register an instance
of it using an `EventDispatcher` object. You can then pass the `EventDispatcher` to the
`MultiDbConfig` constructor to enable the custom behavior.

[code example]

title: Health check configuration

text:
Each health check consists of one or more separate "probes", each of which is a simple
test (such as a [`PING`]() command) to determine if the
database is available. The results of the separate probes are combined
using a configurable policy to determine if the database is healthy.

Note that
an initial health check is performed on each database when you create the client or when
you add a new database. This initial health check has a separate configuration from the
periodic health checks, so you can use a stricter policy here to ensure you don't add
a database that is already unhealthy.

`MultiDbConfig` provides the following options to configure the health check behavior:

| Option | Description |
| --- | --- |
| `health_check_interval` | Time interval between successive health checks (each of which may consist of multiple probes). Default is `5` seconds. |
| `health_check_probes` | Number of separate probes performed during each health check. Default is `3`. |
| `health_check_probes_delay` | Delay between probes during a health check. Default is `0.5` seconds. |
| `health_check_timeout` | Timeout for the full health check operation (including all probes). |
| `health_check_policy` | `HealthCheckPolicies` enum value to specify the policy for determining database health from the separate probes of a health check. The options are `HealthCheckPolicies.HEALTHY_ALL` (all probes must succeed), `HealthCheckPolicies.HEALTHY_ANY` (at least one probe must succeed), and `HealthCheckPolicies.HEALTHY_MAJORITY` (more than half the probes must succeed). The default policy is `HealthCheckPolicies.HEALTHY_MAJORITY`. |
| `health_check` | Custom list of `HealthCheck` objects to specify how to perform each probe during a health check. This defaults to just the simple [`PingHealthCheck`](#pinghealthcheck-default). |
| `initial_health_check_policy` | `InitialHealthCheck` enum value to specify the policy to use during the initial health check. The options are `InitialHealthCheck.ALL_AVAILABLE` (all probes must succeed), `InitialHealthCheck.ANY_AVAILABLE` (at least one probe must succeed), and `InitialHealthCheck.MAJORITY_AVAILABLE` (more than half the probes must succeed). The default policy is `InitialHealthCheck.ALL_AVAILABLE`. |

There are several strategies available for health checks that you can configure using the
`MultiClusterClientConfig` builder. The sections below explain these strategies
in more detail.

title: `PingHealthCheck` (default)

text:
The default strategy, `PingHealthCheck`, periodically sends a Redis
[`PING`]() command
and checks that it gives the expected response. Any unexpected response
or exception indicates an unhealthy server. Although `PingHealthCheck` is
very simple, it is a good basic approach for most Redis deployments.

title: `LagAwareHealthCheck` (Redis Software only) {#lag-aware-health-check}

text:
`LagAwareHealthCheck` is designed specifically for
Redis Software [Active-Active]()
deployments. It determines the health of the server by using the
[REST API]() to check the
synchronization lag between a specific database and the others in the Active-Active
setup. If the lag is within a specified tolerance, the server is considered healthy.

`LagAwareHealthCheck` uses the `health_check_url` value for the endpoint
to connect to the database's REST API, so you must specify this in
the `DatabaseConfig` for each endpoint:

[code example]

You must also add a `LagAwareHealthCheck` instance to the `health_check` list in
the `MultiDbConfig` constructor:

[code example]

The `LagAwareHealthCheck` constructor accepts the following options:

| Option | Description |
| --- | --- |
| `rest_api_port` | Port number for Redis Software REST API (default is 9443). |
| `lag_aware_tolerance` | Tolerable synchronization lag between databases in milliseconds (default is 100ms). |
| `timeout` | REST API request timeout in seconds (default is 30 seconds). |
| `auth_basic` | Tuple of (username, password) for basic authentication. |
| `verify_tls` | Whether to verify TLS certificates (defaults to `True`). |
| `ca_file` | Path to CA certificate file for TLS verification. |
| `ca_path` | Path to CA certificates directory for TLS verification. |
| `ca_data` | CA certificate data as string or bytes. |
| `client_cert_file` | Path to client certificate file for mutual TLS. |
| `client_key_file` | Path to client private key file for mutual TLS. |
| `client_key_password` | Password for encrypted client private key |

title: Custom health check strategy

text:
You can supply your own custom health check strategy by
deriving a new class from the `AbstractHealthCheck` class.
For example, you might use this to integrate with external monitoring tools or
to implement checks that are specific to your application. Add an
instance of your custom class to the `health_check` list in
the `MultiDbConfig` constructor, as with [`LagAwareHealthCheck`](#lag-aware-health-check).
Note that health checks are executed in an asyncio event loop, so you
must implement the `check_health()` method as an async method.

The example below
shows a simple custom strategy that sends a Redis [`ECHO`]()
command and checks for the expected response.

[code example]

title: Managing databases at runtime

text:
Although you will typically configure all databases during the
initial connection, you can also modify the configuration at runtime.
You can add and remove database endpoints, and update their weights:

[code example]

title: Manual failback

text:
By default, the failback mechanism runs health checks on all servers in the weighted list and selects the highest-weighted server that is healthy. However, you can also use the `set_active_database()` method of `MultiDBClient` to select which database to use manually:

[code example]

Note that `set_active_database()` is thread-safe.

If you decide to implement manual failback, you will need a way for external systems to trigger this method in your application. For example, if your application exposes a REST API, you might consider creating a REST endpoint to call `set_active_database()`.

title: Behavior when all endpoints are unhealthy

text:
In the extreme case where no endpoint is healthy, a command will throw a `TemporaryUnavailableException`.
This indicates that the client is periodically checking to see if any endpoint becomes healthy again. The number of
times it will keep checking is configured by the `failover_attempts` option in `MultiDbConfig` and
the delay between attempts is configured by the `failover_delay` option (see [General failover configuration](#general-failover-configuration)). With the default settings, `failover_attempts` * `failover_delay`
gives a period of 120 seconds to find a healthy endpoint.

You can still keep retrying commands after a `TemporaryUnavailableException` is thrown (for example,
you could add this exception to the `supported_errors` list in your `Retry` configuration, as described
in [Retries]()). However, if the client exhausts
all the available failover attempts before any endpoint becomes healthy again, commands will throw a `NoValidDatabaseException`. The client won't recover automatically from this situation, so you
should handle it by reconnecting with the `MultiDBClient` constructor after a suitable delay (see
[Failover configuration](#failover-configuration) for a connection example).

title: Troubleshooting

text: This section lists some common problems and their solutions.

title: Excessive or constant health check failures

text:
If all health checks fail, you should first rule out authentication
problems with the Redis server and also make sure there are no persistent
network connectivity problems.

If you are using [`PingHealthCheck`](#pinghealthcheck-default) or a
[custom health check strategy](#custom-health-check-strategy),
check that the `socket_timeout` is not too low for your network conditions
(see [Timeouts]() for more information).

For
[`LagAwareHealthCheck`](#lag-aware-health-check), check that the `health_check_url`
is set correctly for each endpoint. Note that health checks might be taking longer to
execute than you anticipated, so make sure your `timeout` setting is not too low.

title: Slow failback after recovery

text:
If failback is too slow after a server recovers, you can try
reducing the `health_check_interval` period and also reducing the `grace_period`
before failback is attempted (see [Health check configuration](#health-check-configuration)
for more information about these options).

code:
from redis.multidb.client import MultiDBClient
from redis.multidb.config import MultiDbConfig, DatabaseConfig

db_configs = [
    DatabaseConfig(
        client_kwargs={"host": "redis-east.example.com", "port": "14000"},
        weight=1.0
    ),
    DatabaseConfig(
        client_kwargs={"host": "redis-west.example.com", "port": "14000"},
        weight=0.5
    ),
]

cfg = MultiDbConfig(databases_config=db_configs)
client = MultiDBClient(cfg)

section_id: failover-configuration

code:
cfg = MultiDbConfig(
    ...
    client_class=redis.asyncio.client.Redis,
    ...
)

section_id: client-configuration

code:
cfg = MultiDbConfig(
    ...
    # Retry failed commands up to three times using exponential backoff
    # with jitter between attempts.
    command_retry=Retry(
        retries=3,
        backoff=ExponentialWithJitterBackoff(base=1, cap=10),
    ),
    ...
)

section_id: retry-configuration

code:
import logging

from redis.multidb.client import MultiDBClient
from redis.multidb.config import MultiDbConfig

from redis.event import EventListenerInterface, EventDispatcher
from redis.multidb.event import ActiveDatabaseChanged
 ...

class LogFailoverEventListener(EventListenerInterface):
    def __init__(self, logger: logging.Logger):
        self.logger = logger

    def listen(self, event: ActiveDatabaseChanged):
        self.logger.warning(
            f"Failover happened. Active database switched from {event.old_database} to {event.new_database}"
        )

event_dispatcher = EventDispatcher()
listener = LogFailoverEventListener(logging.getLogger(__name__))

# Register custom listener
event_dispatcher.register_listeners(
    {
        ActiveDatabaseChanged: [listener],
    }
)

config = MultiDbConfig(
    event_dispatcher=event_dispatcher,
    ...
)

client = MultiDBClient(config)

section_id: failover-callbacks

code:
db_configs = [
    DatabaseConfig(
        client_kwargs={"host": "redis-east.example.com", "port": "14000"},
        weight=1.0,
        health_check_url="https://health.redis-east.example.com"
    ),
    DatabaseConfig(
        client_kwargs={"host": "redis-west.example.com", "port": "14000"},
        weight=0.5,
        health_check_url="https://health.redis-west.example.com"
    ),
]

code:
cfg = MultiDbConfig(
    databases_config=db_configs,
    health_check=[LagAwareHealthCheck(
        rest_api_port=9443,
        lag_aware_tolerance=100,  # ms
        verify_tls=True,
        # auth_basic=("user", "pass"),
        # ca_file="/path/ca.pem",
        # client_cert_file="/path/cert.pem",
        # client_key_file="/path/key.pem",
    )],
    ...
)

client = MultiDBClient(cfg)

code:
from redis.asyncio.multidb.healthcheck import AbstractHealthCheck, AsyncRedisClientT

    class EchoHealthCheck(AbstractHealthCheck):
        """
        Health check based on ECHO command.
        """

        async def check_health(self, database, hc_client: AsyncRedisClientT) -> bool:
            await connection.send_command("ECHO", "healthcheck")
            response = await connection.read_response()
            return response in (b"healthcheck", "healthcheck")

cfg = MultiDbConfig(
    ...
    health_check=[EchoHealthCheck(retry=Retry(retries=3))],
    ...
)

client = MultiDBClient(cfg)

section_id: custom-health-check-strategy

code:
from redis.multidb.client import MultiDBClient
from redis.multidb.config import MultiDbConfig, DatabaseConfig

cfg = MultiDbConfig(
    databases_config = [
        DatabaseConfig(
            client_kwargs={"host": "redis-east.example.com", "port": "14000"},
            weight=1.0
        ),
        DatabaseConfig(
            client_kwargs={"host": "redis-west.example.com", "port": "14000"},
            weight=0.5
        ),
    ]
)
client = MultiDBClient(cfg)

# Add a database programmatically. The `skip_initial_health_check`
# parameter defaults to `True`, which avoids adding the database if it
# is unhealthy, but you can override this by setting it to `False`
# explicitly.
other = DatabaseConfig(
    client_kwargs={"host": "redis-south.example.com", "port": "14000"},
    weight=0.5
)
client.add_database(other, skip_initial_health_check=True)

# Get a list of databases, sorted by weight.
databases = client.get_databases()

# Update the weights of each database whose weight is less than 1.
for db in databases:
    if db.weight < 1.0:
        client.update_database_weight(db, 0.9)

# Remove a database from the failover set.
client.remove_database(databases[0])

section_id: managing-databases-at-runtime

code:
# Manually set `other` as the active database.
client.set_active_database(other)

section_id: manual-failback


title: redis-py guide (Python)

title: Connect to the server

title: Connect to Azure Managed Redis

title: Index and query documents

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

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

title: Scan iteration

title: Error handling

title: Client-side geographic failover

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

title: Production usage

summary: Monitor your client's activity for optimization and debugging.

title: Observability

title: Overview

text:
[redis-py](https://github.com/redis/redis-py) is the Python client for Redis.
The sections below explain how to install `redis-py` and connect your application
to a Redis database.

`redis-py` requires a running Redis server. See [here]() for Redis Open Source installation instructions.

You can also access Redis with an object-mapping client interface. See
[RedisOM for Python]()
for more information.

title: Install

text:
To install `redis-py`, enter:

[code example]

For faster performance, install Redis with [`hiredis`](https://github.com/redis/hiredis) support. This provides a compiled response parser, and for most cases requires zero code changes. By default, if `hiredis` >= 1.0 is available, `redis-py` attempts to use it for response parsing.

The Python `distutils` packaging scheme is no longer part of Python 3.12 and greater. If you're having difficulties getting `redis-py` installed in a Python 3.12 environment, consider updating to a recent release of `redis-py`.

[code example]

title: Connect and test

text:
Connect to localhost on port 6379, set a value in Redis, and retrieve it. All responses are returned as bytes in Python. To receive decoded strings, set `decode_responses=True`. For more connection options, see [these examples](https://redis.readthedocs.io/en/stable/examples.html).

Store and retrieve a simple string.

Store and retrieve a dict.

Close the connection when you're done.

title: More information

text:
The [`redis-py`](https://redis.readthedocs.io/en/stable/index.html) website
has a [command reference](https://redis.readthedocs.io/en/stable/commands.html)
and some [tutorials](https://redis.readthedocs.io/en/stable/examples.html) for
various tasks. There are also some examples in the
[GitHub repository](https://github.com/redis/redis-py) for `redis-py`.

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

code: pip install redis

section_id: install

code: pip install redis[hiredis]


title: Observability

title: Installation

text:
Install OTel support for `redis-py` with the following command:

[code example]

title: Import

text:
Start by importing the required OTel and Redis modules:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Go, Python

**Go:**

[code example]

**Python:**

[code example]

title: Configure the meter provider

role: configuration

text:
Otel uses a [Meter provider](https://opentelemetry.io/docs/concepts/signals/metrics/#meter-provider)
to create the objects that collect the metric information. The example below
configures a meter provider to export metrics to a local Grafana instance
every 10 seconds, but see the [OpenTelemetry Python docs](https://opentelemetry.io/docs/languages/python/)
to learn more about other export options.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Go, Python

**Go:**

[code example]

**Python:**

[code example]

title: Configure the Redis client

text:
You configure the client library for OTel only once per application. This will
enable OTel for all Redis connections you create. The example below shows the
options you can pass to the observability instance via the `OTelConfig` object
during initialization.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Go, Python

**Go:**

[code example]

**Python:**

[code example]

The available options for `OTelConfig` are described in the table below:

| Option | Type | Description |
| --- | --- | --- |
| `metric_groups` | `List[MetricGroup]` | List of metric groups to enable. By default, only `CONNECTION_BASIC` and `RESILIENCY` are enabled. See [Redis metric groups]() for a list of available groups. |
| `include_commands` | `List[str]` | List of Redis commands to track. If set, only these commands will be tracked. Note that you should use the Redis command name rather than the Python method name where the two differ. |
| `exclude_commands` | `List[str]` | List of Redis commands to exclude from tracking. If set, all commands except these will be tracked. Note that you should use the Redis command name rather than the Python method name where the two differ. |
| `hide_pubsub_channel_names` | `bool` | If true, channel names in pub/sub metrics will be hidden. |
| `hide_stream_names` | `bool` | If true, stream names in streaming metrics will be hidden. |
| `buckets_operation_duration` | `List[float]` | List of bucket boundaries for the [`operation.duration`]() histogram (see [Custom histogram buckets](#custom-histogram-buckets) below). |
| `buckets_stream_processing_duration` | `List[float]` | List of bucket boundaries for the [`stream.processing.duration`]() histogram (see [Custom histogram buckets](#custom-histogram-buckets) below). |
| `buckets_connection_create_time` | `List[float]` | List of bucket boundaries for the [`connection.create.time`]() histogram (see [Custom histogram buckets](#custom-histogram-buckets) below). |
| `buckets_connection_wait_time` | `List[float]` | List of bucket boundaries for the [`connection.wait.time`]() histogram (see [Custom histogram buckets](#custom-histogram-buckets) below). |

title: Custom histogram buckets

text:
For the histogram metrics, a reasonable default set of buckets is defined, but
you can customize the bucket boundaries to suit your needs (the buckets are the
ranges of data values counted for each bar of the histogram). Pass an increasing
list of float values to the `buckets_xxx` options when you create the `OTelConfig`
object. The first and last values in the list are the lower and upper bounds of the
histogram, respectively, and the values in between define the bucket boundaries.

title: Use Redis

text:
Once you have configured the client, all Redis connections you create will be
automatically instrumented and the collected metrics will be exported to your
configured destination.

The example below shows the simplest Redis connection and a few commands,
but see the
[observability demonstration repository](https://github.com/redis-developer/redis-client-observability)
for an example that calls a variety of commands in a more realistic way.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Go, Python

**Go:**

[code example]

**Python:**

[code example]

title: Shutdown

text:
When your application exits, you should call the `shutdown()` method to ensure
that all pending metrics are exported.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, Go, Python

**Go:**

[code example]

**Python:**

[code example]

code: pip install redis[otel]

section_id: installation

code:
import (
	"context"
	"fmt"
	"time"

	"github.com/redis/go-redis/extra/redisotel-native/v9"
	"github.com/redis/go-redis/v9"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
	"go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/resource"
	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

section_id: import

code:
# OpenTelemetry metrics API
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter

# Redis observability API
from redis.observability.providers import get_observability_instance
from redis.observability.config import OTelConfig, MetricGroup

# Redis client
import redis

code:
ctx := context.Background()

	// Create OTLP exporter that sends metrics to the collector
	// Default endpoint is localhost:4317 (gRPC)
	exporter, err := otlpmetricgrpc.New(ctx,
		otlpmetricgrpc.WithInsecure(), // Use insecure for local development
		// For production, configure TLS and authentication:
		// otlpmetricgrpc.WithEndpoint("your-collector:4317"),
		// otlpmetricgrpc.WithTLSCredentials(...),
	)

	if err != nil {
		panic(err)
	}

	// Create resource with service name
	res, err := resource.New(ctx,
		resource.WithAttributes(
			semconv.ServiceName(
				fmt.Sprintf("go-redis-examples:%d", time.Now().Unix()),
			),
		),
	)

	if err != nil {
		panic(err)
	}

	// Create meter provider with periodic reader
	// Metrics are exported every 10 seconds
	meterProvider := metric.NewMeterProvider(
		metric.WithResource(res),
		metric.WithReader(
			metric.NewPeriodicReader(exporter,
				metric.WithInterval(10*time.Second),
			),
		),
	)

	// Set the global meter provider
	otel.SetMeterProvider(meterProvider)

section_id: configure-the-meter-provider

code:
exporter = OTLPMetricExporter(endpoint="http://localhost:4318/v1/metrics")
reader = PeriodicExportingMetricReader(exporter=exporter, export_interval_millis=10000)
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)

code:
// Initialize OTel instrumentation BEFORE creating Redis clients
	otelInstance := redisotel.GetObservabilityInstance()
	config := redisotel.NewConfig().
		// You must enable OTel explicitly
		WithEnabled(true).
		// Enable the metric groups you want to collect. Use bitwise OR
		// to combine multiple groups. The default is `MetricGroupAll`
		// which includes all groups.
		WithMetricGroups(redisotel.MetricGroupFlagCommand |
			redisotel.MetricGroupFlagConnectionBasic |
			redisotel.MetricGroupFlagResiliency |
			redisotel.MetricGroupFlagConnectionAdvanced).
		// Filter which commands to track
		WithIncludeCommands([]string{"GET", "SET"}).
		WithExcludeCommands([]string{"DEBUG", "SLOWLOG"}).
		// Privacy controls
		WithHidePubSubChannelNames(true).
		WithHideStreamNames(true).
		// Custom histogram buckets
		WithHistogramBuckets([]float64{
			0.0001, // 0.1ms
			0.0005, // 0.5ms
			0.001,  // 1ms
			0.005,  // 5ms
			0.01,   // 10ms
			0.05,   // 50ms
			0.1,    // 100ms
			0.5,    // 500ms
			1.0,    // 1s
			5.0,    // 5s
			10.0,   // 10s
		})

	if err := otelInstance.Init(config); err != nil {
		panic(err)
	}

section_id: configure-the-redis-client

code:
otel = get_observability_instance()
otel.init(OTelConfig(
    # Metric groups to enable (default: CONNECTION_BASIC | RESILIENCY)
    metric_groups=[
        MetricGroup.CONNECTION_BASIC,    # Connection creation time, relaxed timeout
        MetricGroup.CONNECTION_ADVANCED, # Connection wait time, timeouts, closed connections
        MetricGroup.COMMAND,             # Command execution duration
        MetricGroup.RESILIENCY,          # Error counts, maintenance notifications
        MetricGroup.PUBSUB,              # PubSub message counts
        MetricGroup.STREAMING,           # Stream message lag
        MetricGroup.CSC,                 # Client Side Caching metrics
    ],

    # Filter which commands to track
    include_commands=['GET', 'SET', 'HGET'],  # Only track these commands
    # OR
    exclude_commands=['DEBUG', 'SLOWLOG'],    # Track all except these

    # Privacy controls
    hide_pubsub_channel_names=True,  # Hide channel names in PubSub metrics
    hide_stream_names=True,          # Hide stream names in streaming metrics

    # Custom histogram buckets
    buckets_operation_duration=[
        0.0001, 0.00025, 0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1,
        0.25, 0.5, 1, 2.5,
    ],
    buckets_stream_processing_duration=[
        0.0001, 0.00025, 0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1,
        0.25, 0.5, 1, 2.5,
    ],
    buckets_connection_create_time=[
        0.0001, 0.00025, 0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1,
        0.25, 0.5, 1, 2.5,
    ],
    buckets_connection_wait_time=[
        0.0001, 0.00025, 0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1,
        0.25, 0.5, 1, 2.5,
    ],
))

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

	rdb.Set(ctx, "key", "value", 0)
	v, err := rdb.Get(ctx, "key").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(v)

section_id: use-redis

code:
r = redis.Redis(host='localhost', port=6379)
r.set('key', 'value')  # Metrics collected automatically
r.get('key')

code:
rdb.Close()
	otelInstance.Shutdown()
	meterProvider.Shutdown(context.Background())

section_id: shutdown