Range queries

Summary: Perform numeric range queries

Start and end values

Start and end values are by default inclusive, but you can prepend `(` to a value to exclude it from the range.

The values `-inf`, `inf`, and `+inf` are valid values that allow you to define open ranges.

Result set

An open-range query can lead to a large result set.

By default, [`FT.SEARCH`]() returns only the first ten results. The `LIMIT` argument helps you to scroll through the result set. The `SORTBY` argument ensures that the documents in the result set are returned in the specified order.

[code example]

You can find further details about using the `LIMIT` and `SORTBY` in the [[`FT.SEARCH`]() command reference](/commands/ft.search/).

Examples

The examples in this section use a schema with the following fields:

| Field name | Field type |
| ---------- | ---------- |
| `price` | `NUMERIC` |

The following query finds bicycles within a price range greater than or equal to 500 USD and smaller than or equal to 1000 USD (`500 <= price <= 1000`):

#### Code Examples

Foundational: Query numeric fields with inclusive range syntax when you need to find documents with values between two bounds

**Difficulty:** Beginner

**Commands:** FT.SEARCH

**Complexity:**
- FT.SEARCH: O(N)

**Redis CLI:**

[code example]

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

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

This is semantically equivalent to:

#### Code Examples

Foundational: Query numeric fields using a FILTER clause when you need an alternative syntax for range queries with different query execution semantics

**Difficulty:** Beginner

**Commands:** FT.SEARCH

**Complexity:**
- FT.SEARCH: O(N)

**Redis CLI:**

[code example]

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

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

For bicycles with a price greater than 1000 USD (`price > 1000`), you can use:

#### Code Examples

Open ranges: Query numeric fields with open ranges using infinity notation and exclusive bounds when you need to find documents above or below a threshold

**Difficulty:** Intermediate

**Commands:** FT.SEARCH

**Complexity:**
- FT.SEARCH: O(N)

**Redis CLI:**

[code example]

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

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

The example below returns bicycles with a price lower than or equal to 2000 USD (`price <= 2000`) by returning the five cheapest bikes:

#### Code Examples

Sorting and pagination: Combine range queries with SORTBY and LIMIT to retrieve sorted results in pages when you need to handle large result sets efficiently

**Difficulty:** Intermediate

**Commands:** FT.SEARCH

**Complexity:**
- FT.SEARCH: O(N)

**Redis CLI:**

[code example]

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

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

Non-numeric range queries

You can learn more about non-numeric range queries, such as [geospatial]() or [vector search]() queries, in their dedicated articles.

FT.SEARCH index "@field:[start end]" SORTBY field LIMIT page_start page_end

> FT.SEARCH idx:bicycle "@price:[500 1000]"
1) (integer) 3
2) "bicycle:2"
3) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, -87.6848 41.8231, -87.6848 41.9331))\",\"store_location\":\"-87.6298,41.8781\",\"brand\":\"Nord\",\"model\":\"Chook air 5\",\"price\":815,\"description\":\"The Chook Air 5  gives kids aged six years and older a durable and uberlight mountain bike for their first experience on tracks and easy cruising through forests and fields. The lower  top tube makes it easy to mount and dismount in any situation, giving your kids greater safety on the trails.\",\"condition\":\"used\"}"
4) "bicycle:5"
5) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, -0.1778 51.4024, -0.1778 51.5524))\",\"store_location\":\"-0.1278,51.5074\",\"brand\":\"Breakout\",\"model\":\"XBN 2.1 Alloy\",\"price\":810,\"description\":\"The XBN 2.1 Alloy is our entry-level road bike \xe2\x80\x93 but that\xe2\x80\x99s not to say that it\xe2\x80\x99s a basic machine. With an internal weld aluminium frame, a full carbon fork, and the slick-shifting Claris gears from Shimano\xe2\x80\x99s, this is a bike which doesn\xe2\x80\x99t break the bank and delivers craved performance.\",\"condition\":\"new\"}"
6) "bicycle:9"
7) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((12.4464 42.1028, 12.5464 42.1028, 12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))\",\"store_location\":\"12.4964,41.9028\",\"model\":\"ThrillCycle\",\"brand\":\"BikeShind\",\"price\":815,\"description\":\"An artsy,  retro-inspired bicycle that\xe2\x80\x99s as functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn\xe2\x80\x99t suggest taking it to the mountains. Fenders protect you from mud, and a rear basket lets you transport groceries, flowers and books. The ThrillCycle comes with a limited lifetime warranty, so this little guy will last you long past graduation.\",\"condition\":\"refurbished\"}"

SearchResult res1 = db.FT().Search(
            "idx:bicycle",
            new("@price:[500 1000]")
        );
        Console.WriteLine(res1.TotalResults);   // >>> 3

res1, err := rdb.FTSearchWithArgs(ctx,
		"idx:bicycle", "@price:[500 1000]",
		&redis.FTSearchOptions{
			Return: []redis.FTSearchReturn{
				{
					FieldName: "price",
				},
			},
			SortBy: []redis.FTSearchSortBy{
				{
					FieldName: "price",
					Asc:       true,
				},
			},
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res1.Total) // >>> 3

	for _, doc := range res1.Docs {
		fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"])
	}
	// >>> bicycle:2 : price 815
	// >>> bicycle:5 : price 810
	// >>> bicycle:9 : price 815

CompletableFuture<SearchReply<String, String>> priceResults = asyncCommands
                    .ftSearch("idx:bicycle", "@price:[500 1000]").thenApply(res -> {
                        res.getResults().stream().sorted((doc1, doc2) -> doc1.getId().compareTo(doc2.getId())).forEach(doc -> {
                            System.out.printf("ID: %s\n", doc.getId());
                        });
                        // >>> ID: bicycle:2
                        // >>> ID: bicycle:5
                        // >>> ID: bicycle:9
                        return res;
                    }).toCompletableFuture();

Mono<SearchReply<String, String>> priceResults = reactiveCommands.ftSearch("idx:bicycle", "@price:[500 1000]")
                    .doOnNext(res -> {
                        res.getResults().stream().sorted((doc1, doc2) -> doc1.getId().compareTo(doc2.getId())).forEach(doc -> {
                            System.out.printf("ID: %s\n", doc.getId());
                        });
                        // >>> ID: bicycle:2
                        // >>> ID: bicycle:5
                        // >>> ID: bicycle:9
                    });

SearchResult res1 = jedis.ftSearch(
            "idx:bicycle", "@price:[500 1000]",
            FTSearchParams.searchParams().returnFields("price"));
        System.out.println(res1.getTotalResults()); // >>> 3

        List<Document> docs1 = res1.getDocuments();

        for (Document document : docs1) {
            System.out.println(document.getId() + " : price " + document.getString("price"));
        }
        // >>> bicycle:2 : price 815
        // >>> bicycle:5 : price 810
        // >>> bicycle:9 : price 815

const res1 = await client.ft.search('idx:bicycle', '@price:[500 1000]');
console.log(res1.total); // >>> 3

res = index.search(Query("@price:[500 1000]"))
print(res.total)
# >>> 3

> FT.SEARCH idx:bicycle "*" FILTER price 500 1000
1) (integer) 3
2) "bicycle:2"
3) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, -87.6848 41.8231, -87.6848 41.9331))\",\"store_location\":\"-87.6298,41.8781\",\"brand\":\"Nord\",\"model\":\"Chook air 5\",\"price\":815,\"description\":\"The Chook Air 5  gives kids aged six years and older a durable and uberlight mountain bike for their first experience on tracks and easy cruising through forests and fields. The lower  top tube makes it easy to mount and dismount in any situation, giving your kids greater safety on the trails.\",\"condition\":\"used\"}"
4) "bicycle:5"
5) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, -0.1778 51.4024, -0.1778 51.5524))\",\"store_location\":\"-0.1278,51.5074\",\"brand\":\"Breakout\",\"model\":\"XBN 2.1 Alloy\",\"price\":810,\"description\":\"The XBN 2.1 Alloy is our entry-level road bike \xe2\x80\x93 but that\xe2\x80\x99s not to say that it\xe2\x80\x99s a basic machine. With an internal weld aluminium frame, a full carbon fork, and the slick-shifting Claris gears from Shimano\xe2\x80\x99s, this is a bike which doesn\xe2\x80\x99t break the bank and delivers craved performance.\",\"condition\":\"new\"}"
6) "bicycle:9"
7) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((12.4464 42.1028, 12.5464 42.1028, 12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))\",\"store_location\":\"12.4964,41.9028\",\"model\":\"ThrillCycle\",\"brand\":\"BikeShind\",\"price\":815,\"description\":\"An artsy,  retro-inspired bicycle that\xe2\x80\x99s as functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn\xe2\x80\x99t suggest taking it to the mountains. Fenders protect you from mud, and a rear basket lets you transport groceries, flowers and books. The ThrillCycle comes with a limited lifetime warranty, so this little guy will last you long past graduation.\",\"condition\":\"refurbished\"}"

SearchResult res2 = db.FT().Search(
            "idx:bicycle",
            new Query().AddFilter(
                new Query.NumericFilter("price", 500, 1000)
            )
        );
        Console.WriteLine(res2.TotalResults);   // >>> 3

res2, err := rdb.FTSearchWithArgs(ctx,
		"idx:bicycle", "*",
		&redis.FTSearchOptions{
			Filters: []redis.FTSearchFilter{
				{
					FieldName: "price",
					Min:       500,
					Max:       1000,
				},
			},
			Return: []redis.FTSearchReturn{
				{
					FieldName: "price",
				},
			},
			SortBy: []redis.FTSearchSortBy{
				{
					FieldName: "price",
					Asc:       true,
				},
			},
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res2.Total) // >>> 3

	for _, doc := range res2.Docs {
		fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"])
	}
	// >>> bicycle:2 : price 815
	// >>> bicycle:5 : price 810
	// >>> bicycle:9 : price 815

SearchResult res2 = jedis.ftSearch("idx:bicycle",
            "*",
            FTSearchParams.searchParams()
                .returnFields("price")
                .filter("price", 500, 1000)
        );
        System.out.println(res2.getTotalResults()); // >>> 3

        List<Document> docs2 = res2.getDocuments();

        for (Document document : docs2) {
            System.out.println(document.getId() + " : price " + document.getString("price"));
        }
        // >>> bicycle:2 : price 815
        // >>> bicycle:5 : price 810
        // >>> bicycle:9 : price 815

// FILTER is not supported
// const res2 = await client.ft.search('idx:bicycle', '*', {
//   FILTER: {
//     field: 'price',
//     min: 500,
//     max: 1000,
//   }
// });
// console.log(res2.total); // >>> 3

query = Query("*").add_filter(NumericFilter("price", 500, 1000))
res = index.search(query)
print(res.total)
# >>> 3

> FT.SEARCH idx:bicycle "@price:[(1000 +inf]"
 1) (integer) 5
 2) "bicycle:1"
 3) 1) "$"
    2) "{\"pickup_zone\":\"POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, -118.2887 33.9872, -118.2887 34.0972))\",\"store_location\":\"-118.2437,34.0522\",\"brand\":\"Bicyk\",\"model\":\"Hillcraft\",\"price\":1200,\"description\":\"Kids want to ride with as little weight as possible. Especially on an incline! They may be at the age when a 27.5\\\" wheel bike is just too clumsy coming off a 24\\\" bike. The Hillcraft 26 is just the solution they need!\",\"condition\":\"used\"}"
 4) "bicycle:4"
 5) 1) "$"
    2) "{\"pickup_zone\":\"POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, -122.4644 37.7099, -122.4644 37.8199))\",\"store_location\":\"-122.4194,37.7749\",\"brand\":\"Noka Bikes\",\"model\":\"Kahuna\",\"price\":3200,\"description\":\"Whether you want to try your hand at XC racing or are looking for a lively trail bike that's just as inspiring on the climbs as it is over rougher ground, the Wilder is one heck of a bike built specifically for short women. Both the frames and components have been tweaked to include a women\xe2\x80\x99s saddle, different bars and unique colourway.\",\"condition\":\"used\"}"
 6) "bicycle:3"
 7) 1) "$"
    2) "{\"pickup_zone\":\"POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, -80.2433 25.6967, -80.2433 25.8067))\",\"store_location\":\"-80.1918,25.7617\",\"brand\":\"Eva\",\"model\":\"Eva 291\",\"price\":3400,\"description\":\"The sister company to Nord, Eva launched in 2005 as the first and only women-dedicated bicycle brand. Designed by women for women, allEva bikes are optimized for the feminine physique using analytics from a body metrics database. If you like 29ers, try the Eva 291. It\xe2\x80\x99s a brand new bike for 2022.. This full-suspension, cross-country ride has been designed for velocity. The 291 has 100mm of front and rear travel, a superlight aluminum frame and fast-rolling 29-inch wheels. Yippee!\",\"condition\":\"used\"}"
 8) "bicycle:6"
 9) 1) "$"
    2) "{\"pickup_zone\":\"POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, 2.1767 48.5516, 2.1767 48.9016))\",\"store_location\":\"2.3522,48.8566\",\"brand\":\"ScramBikes\",\"model\":\"WattBike\",\"price\":2300,\"description\":\"The WattBike is the best e-bike for people who still feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one charge. It\xe2\x80\x99s great for tackling hilly terrain or if you just fancy a more leisurely ride. With three working modes, you can choose between E-bike, assisted bicycle, and normal bike modes.\",\"condition\":\"new\"}"
10) "bicycle:8"
11) 1) "$"
    2) "{\"pickup_zone\":\"POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, 1.9450 41.1987, 1.9450 41.4301))\",\"store_location\":\"2.1734, 41.3851\",\"brand\":\"nHill\",\"model\":\"Summit\",\"price\":1200,\"description\":\"This budget mountain bike from nHill performs well both on bike paths and on the trail. The fork with 100mm of travel absorbs rough terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. The Shimano Tourney drivetrain offered enough gears for finding a comfortable pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. Whether you want an affordable bike that you can take to work, but also take trail in mountains on the weekends or you\xe2\x80\x99re just after a stable, comfortable ride for the bike path, the Summit gives a good value for money.\",\"condition\":\"new\"}"

SearchResult res3 = db.FT().Search(
            "idx:bicycle",
            new Query("*").AddFilter(new Query.NumericFilter(
                    "price", 1000, true, Double.PositiveInfinity, false
                )
            )
        );
        Console.WriteLine(res3.TotalResults);   // >>> 5

res3, err := rdb.FTSearchWithArgs(ctx,
		"idx:bicycle", "*",
		&redis.FTSearchOptions{
			Return: []redis.FTSearchReturn{
				{
					FieldName: "price",
				},
			},
			SortBy: []redis.FTSearchSortBy{
				{
					FieldName: "price",
					Asc:       true,
				},
			},
			Filters: []redis.FTSearchFilter{
				{
					FieldName: "price",
					Min:       "(1000",
					Max:       "+inf",
				},
			},
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res3.Total) // >>> 5

	for _, doc := range res3.Docs {
		fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"])
	}
	// >>> bicycle:1 : price 1200
	// >>> bicycle:4 : price 3200
	// >>> bicycle:6 : price 2300
	// >>> bicycle:3 : price 3400
	// >>> bicycle:8 : price 1200

SearchArgs<String, String> priceSearchArgs2 = SearchArgs.<String, String> builder().returnField("price").build();

            CompletableFuture<SearchReply<String, String>> priceResults2 = asyncCommands
                    .ftSearch("idx:bicycle", "@price:[(1000 +inf]", priceSearchArgs2).thenApply(res -> {
                        res.getResults().stream().sorted((doc1, doc2) -> doc1.getId().compareTo(doc2.getId())).forEach(doc -> {
                            System.out.printf("ID: %s, price: %s\n", doc.getId(), doc.getFields().get("price"));
                        });
                        // >>> ID: bicycle:1, price: 1200
                        // >>> ID: bicycle:3, price: 3400
                        // >>> ID: bicycle:4, price: 3200
                        // >>> ID: bicycle:6, price: 2300
                        // >>> ID: bicycle:8, price: 1200
                        return res;
                    }).toCompletableFuture();

SearchArgs<String, String> priceSearchArgs2 = SearchArgs.<String, String> builder().returnField("price").build();

            Mono<SearchReply<String, String>> priceResults2 = reactiveCommands
                    .ftSearch("idx:bicycle", "@price:[(1000 +inf]", priceSearchArgs2).doOnNext(res -> {
                        res.getResults().stream().sorted((doc1, doc2) -> doc1.getId().compareTo(doc2.getId())).forEach(doc -> {
                            System.out.printf("ID: %s, price: %s\n", doc.getId(), doc.getFields().get("price"));
                        });
                        // >>> ID: bicycle:1, price: 1200
                        // >>> ID: bicycle:3, price: 3400
                        // >>> ID: bicycle:4, price: 3200
                        // >>> ID: bicycle:6, price: 2300
                        // >>> ID: bicycle:8, price: 1200
                    });

SearchResult res3 = jedis.ftSearch("idx:bicycle",
            "*",
            FTSearchParams.searchParams()
                .returnFields("price")
                .filter("price", 1000, true, Double.POSITIVE_INFINITY, false)
        );
        System.out.println(res3.getTotalResults()); // >>> 5

        List<Document> docs3 = res3.getDocuments();

        for (Document document : docs3) {
            System.out.println(document.getId() + " : price " + document.getString("price"));
        }
        // >>> bicycle:1 : price 1200
        // >>> bicycle:4 : price 3200
        // >>> bicycle:6 : price 2300
        // >>> bicycle:3 : price 3400
        // >>> bicycle:8 : price 1200

// FILTER is not supported
// const res3 = await client.ft.search('idx:bicycle', '*', {
//   FILTER: {
//     field: 'price',
//     min: '(1000',
//     max: '+inf,
//   }
// });
// console.log(res3.total); // >>> 5

query = Query("*").add_filter(NumericFilter("price", "(1000", "+inf"))
res = index.search(query)
print(res.total)
# >>> 5

> FT.SEARCH idx:bicycle "@price:[-inf 2000]" SORTBY price LIMIT 0 5
 1) (integer) 7
 2) "bicycle:0"
 3) 1) "price"
    2) "270"
    3) "$"
    4) "{\"pickup_zone\":\"POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, -74.0610 40.6678, -74.0610 40.7578))\",\"store_location\":\"-74.0060,40.7128\",\"brand\":\"Velorim\",\"model\":\"Jigger\",\"price\":270,\"description\":\"Small and powerful, the Jigger is the best ride for the smallest of tikes! This is the tiniest kids\xe2\x80\x99 pedal bike on the market available without a coaster brake, the Jigger is the vehicle of choice for the rare tenacious little rider raring to go.\",\"condition\":\"new\"}"
 4) "bicycle:7"
 5) 1) "price"
    2) "430"
    3) "$"
    4) "{\"pickup_zone\":\"POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, 13.3260 52.2700, 13.3260 52.5700))\",\"store_location\":\"13.4050,52.5200\",\"brand\":\"Peaknetic\",\"model\":\"Secto\",\"price\":430,\"description\":\"If you struggle with stiff fingers or a kinked neck or back after a few minutes on the road, this lightweight, aluminum bike alleviates those issues and allows you to enjoy the ride. From the ergonomic grips to the lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. The rear-inclined seat tube facilitates stability by allowing you to put a foot on the ground to balance at a stop, and the low step-over frame makes it accessible for all ability and mobility levels. The saddle is very soft, with a wide back to support your hip joints and a cutout in the center to redistribute that pressure. Rim brakes deliver satisfactory braking control, and the wide tires provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts facilitate setting up the Roll Low-Entry as your preferred commuter, and the BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.\",\"condition\":\"new\"}"
 6) "bicycle:5"
 7) 1) "price"
    2) "810"
    3) "$"
    4) "{\"pickup_zone\":\"POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, -0.1778 51.4024, -0.1778 51.5524))\",\"store_location\":\"-0.1278,51.5074\",\"brand\":\"Breakout\",\"model\":\"XBN 2.1 Alloy\",\"price\":810,\"description\":\"The XBN 2.1 Alloy is our entry-level road bike \xe2\x80\x93 but that\xe2\x80\x99s not to say that it\xe2\x80\x99s a basic machine. With an internal weld aluminium frame, a full carbon fork, and the slick-shifting Claris gears from Shimano\xe2\x80\x99s, this is a bike which doesn\xe2\x80\x99t break the bank and delivers craved performance.\",\"condition\":\"new\"}"
 8) "bicycle:2"
 9) 1) "price"
    2) "815"
    3) "$"
    4) "{\"pickup_zone\":\"POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, -87.6848 41.8231, -87.6848 41.9331))\",\"store_location\":\"-87.6298,41.8781\",\"brand\":\"Nord\",\"model\":\"Chook air 5\",\"price\":815,\"description\":\"The Chook Air 5  gives kids aged six years and older a durable and uberlight mountain bike for their first experience on tracks and easy cruising through forests and fields. The lower  top tube makes it easy to mount and dismount in any situation, giving your kids greater safety on the trails.\",\"condition\":\"used\"}"
10) "bicycle:9"
11) 1) "price"
    2) "815"
    3) "$"
    4) "{\"pickup_zone\":\"POLYGON((12.4464 42.1028, 12.5464 42.1028, 12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))\",\"store_location\":\"12.4964,41.9028\",\"model\":\"ThrillCycle\",\"brand\":\"BikeShind\",\"price\":815,\"description\":\"An artsy,  retro-inspired bicycle that\xe2\x80\x99s as functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn\xe2\x80\x99t suggest taking it to the mountains. Fenders protect you from mud, and a rear basket lets you transport groceries, flowers and books. The ThrillCycle comes with a limited lifetime warranty, so this little guy will last you long past graduation.\",\"condition\":\"refurbished\"}"

SearchResult res4 = db.FT().Search(
            "idx:bicycle",
            new Query("@price:[-inf 2000]")
                .SetSortBy("price")
                .Limit(0, 5)
        );
        Console.WriteLine(res4.TotalResults);   // >>> 7
        Console.WriteLine($"Prices: {string.Join(", ", res4.Documents.Select(d => d["price"]))}");
        // >>> Prices: 270, 430, 810, 815, 815

res4, err := rdb.FTSearchWithArgs(ctx,
		"idx:bicycle",
		"@price:[-inf 2000]",
		&redis.FTSearchOptions{
			Return: []redis.FTSearchReturn{
				{
					FieldName: "price",
				},
			},
			SortBy: []redis.FTSearchSortBy{
				{
					FieldName: "price",
					Asc:       true,
				},
			},
			LimitOffset: 0,
			Limit:       5,
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res4.Total) // >>> 7

	for _, doc := range res4.Docs {
		fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"])
	}
	// >>> bicycle:0 : price 270
	// >>> bicycle:7 : price 430
	// >>> bicycle:5 : price 810
	// >>> bicycle:2 : price 815
	// >>> bicycle:9 : price 815

SearchArgs<String, String> priceSearchArgs3 = SearchArgs.<String, String> builder().returnField("price")
                    .sortBy(SortByArgs.<String> builder().attribute("price").build()).limit(0, 5).build();

            CompletableFuture<SearchReply<String, String>> priceResults3 = asyncCommands
                    .ftSearch("idx:bicycle", "@price:[-inf 2000]", priceSearchArgs3).thenApply(res -> {
                        res.getResults().stream().sorted((doc1, doc2) -> doc1.getId().compareTo(doc2.getId())).forEach(doc -> {
                            System.out.printf("ID: %s, price: %s\n", doc.getId(), doc.getFields().get("price"));
                        });
                        // >>> ID: bicycle:0, price: 270
                        // >>> ID: bicycle:2, price: 815
                        // >>> ID: bicycle:5, price: 810
                        // >>> ID: bicycle:7, price: 430
                        // >>> ID: bicycle:9, price: 815
                        return res;
                    }).toCompletableFuture();

SearchArgs<String, String> priceSearchArgs3 = SearchArgs.<String, String> builder().returnField("price")
                    .sortBy(SortByArgs.<String> builder().attribute("price").build()).limit(0, 5).build();

            Mono<SearchReply<String, String>> priceResults3 = reactiveCommands
                    .ftSearch("idx:bicycle", "@price:[-inf 2000]", priceSearchArgs3).doOnNext(res -> {
                        res.getResults().stream().sorted((doc1, doc2) -> doc1.getId().compareTo(doc2.getId())).forEach(doc -> {
                            System.out.printf("ID: %s, price: %s\n", doc.getId(), doc.getFields().get("price"));
                        });
                        // >>> ID: bicycle:0, price: 270
                        // >>> ID: bicycle:2, price: 815
                        // >>> ID: bicycle:5, price: 810
                        // >>> ID: bicycle:7, price: 430
                        // >>> ID: bicycle:9, price: 815
                    });

SearchResult res4 = jedis.ftSearch("idx:bicycle",
            "@price:[-inf 2000]",
            FTSearchParams.searchParams()
                    .returnFields("price")
                    .sortBy("price", SortingOrder.ASC)
                    .limit(0, 5)
        );
        System.out.println(res4.getTotalResults()); // >>> 7

        List<Document> docs4 = res4.getDocuments();

        for (Document document : docs4) {
            System.out.println(document.getId() + " : price " + document.getString("price"));
        }
        // >>> bicycle:0 : price 270
        // >>> bicycle:7 : price 430
        // >>> bicycle:5 : price 810
        // >>> bicycle:2 : price 815
        // >>> bicycle:9 : price 815

const res4 = await client.ft.search(
  'idx:bicycle',
  '@price:[-inf 2000]',
  {
    SORTBY: 'price',
    LIMIT: { from: 0, size: 5 }
  }
);
console.log(res4.total); // >>> 7
console.log(res4); // >>> { total: 7, documents: [ { id: 'bicycle:0', value: [Object: null prototype] }, { id: 'bicycle:7', value: [Object: null prototype] }, { id: 'bicycle:5', value: [Object: null prototype] }, { id: 'bicycle:2', value: [Object: null prototype] }, { id: 'bicycle:9', value: [Object: null prototype] } ] }

query = Query('@price:[-inf 2000]').sort_by('price').paging(0, 5)
res = index.search(query)
print(res.total)
print(res)
# >>> Result{7 total, docs: [Document {'id': 'bicycle:0', ... }, Document {'id': 'bicycle:7', ... }, Document {'id': 'bicycle:5', ... }, ...]


Vector search

Summary: Query for data based on vector embeddings

K-neareast neighbours (KNN)

The Redis command [FT.SEARCH]() takes the index name, the query string, and additional query parameters as arguments. You need to pass the number of nearest neighbors, the vector field name, and the vector's binary representation in the following way:

[code example]

Here is a more detailed explanation of this query:

1. **Pre-filter**: The first expression within the round brackets is a filter. It allows you to decide which vectors should be taken into account before the vector search is performed. The expression `(*)` means that all vectors are considered.
2. **Next step**: The `=>` arrow indicates that the pre-filtering happens before the vector search.
3. **KNN query**: The expression `[KNN num_neighbours @field $vector]` is a parameterized query expression. A parameter name is indicated by the `$` prefix within the query string.
4. **Vector binary data**: You need to use the `PARAMS` argument to substitute `$vector` with the binary representation of the vector. The value `2` indicates that `PARAMS` is followed by two arguments, the parameter name `vector` and the parameter value.
5. **Dialect**: The vector search feature has been available since version two of the query dialect.

You can read more about the `PARAMS` argument in the [FT.SEARCH]() command reference.

The following example shows you how to query for three bikes based on their description embeddings, and by using the field alias `vector`. The result is returned in ascending order based on the distance. You can see that the query only returns the fields `__vector_score` and `description`. The field `__vector_score` is present by default. Because you can have multiple vector fields in your schema, the vector score field name depends on the name of the vector field. If you change the field name `@vector` to `@foo`, the score field name changes to `__foo_score`.

#### Code Examples

K-nearest neighbors: Find the k closest vectors to a query vector using KNN when you need to retrieve similar items based on embeddings

**Difficulty:** Beginner

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js), Python

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

<!-- Python query>
query = (
    Query('(*)=>[KNN 3 @vector $query_vector]')
     .sort_by('__vector_score')
     .return_fields('__vector_score', 'description')
     .dialect(2)
)
</!-->

The binary value of the query vector is significantly shortened in the CLI example above.

Radius

Instead of the number of nearest neighbors, you need to pass the radius along with the index name, the vector field name, and the vector's binary value:

[code example]

If you want to sort by distance, then you must yield the distance via the range query parameter `$YIELD_DISTANCE_AS`:

[code example]

Here is a more detailed explanation of this query:

1. **Range query**: the syntax of a radius query is very similar to the regular range query, except for the keyword `VECTOR_RANGE`. You can also combine a vector radius query with other queries in the same way as regular range queries.  See [combined queries article]() for more details.
2. **Additional step**: the `=>` arrow means that the range query is followed by evaluating additional parameters.
3. **Range query parameters**: parameters such as `$YIELD_DISTANCE_AS` can be found in the [vectors reference documentation]().
4. **Vector binary data**: you need to use `PARAMS` to pass the binary representation of the vector.
5. **Dialect**: vector search has been available since version two of the query dialect.

By default, [`FT.SEARCH`]() returns only the first ten results. The [range query article]() explains to you how to scroll through the result set.

The example below shows a radius query that returns the description and the distance within a radius of `0.5`. The result is sorted by the distance.

#### Code Examples

Radius search: Find all vectors within a specified distance threshold using VECTOR_RANGE when you need to retrieve similar items with a distance constraint

**Difficulty:** Intermediate

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js), Python

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

<!-- Python query>
query = (
    Query('@vector:[VECTOR_RANGE 0.5 $query_vector]=>{$YIELD_DISTANCE_AS: vector_dist}')
     .sort_by('vector_dist')
     .return_fields('vector_dist', 'description')
     .dialect(2)
)
</!-->

Cluster optimization

In Redis cluster environments, you can optimize vector search performance using the `$SHARD_K_RATIO` query attribute. This parameter controls how many results each shard retrieves relative to the requested `top_k`, creating a tunable trade-off between accuracy and performance.

Basic cluster optimization

Retrieve 100 nearest neighbors with each shard providing 60% of the requested results:

#### Code Examples

Cluster optimization: Optimize KNN queries in cluster environments using SHARD_K_RATIO to balance accuracy and performance when you need efficient distributed vector search

**Difficulty:** Advanced

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js), Python

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

Combined with filtering

You can combine `$SHARD_K_RATIO` with pre-filtering to optimize searches on specific subsets of data:

#### Code Examples

Filtered vector search: Combine pre-filtering with KNN and cluster optimization to search within a subset of data when you need to find similar items matching specific criteria

**Difficulty:** Advanced

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js), Python

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

The `$SHARD_K_RATIO` parameter is only applicable in Redis cluster environments and has no effect in standalone Redis instances.

FT.SEARCH index "(*)=>[KNN num_neighbours @field $vector]" PARAMS 2 vector "binary_data" DIALECT 2

FT.SEARCH idx:bikes_vss "(*)=>[KNN 3 @vector $query_vector]" PARAMS 2 "query_vector" "Z\xf8\x15:\xf23\xa1\xbfZ\x1dI>\r\xca9..." SORTBY "__vector_score" ASC RETURN 2 "__vector_score" "description" DIALECT 2

FT.SEARCH index "@field:[VECTOR_RANGE radius $vector]" PARAMS 2 vector "binary_data" DIALECT 2

FT.SEARCH index "@field:[VECTOR_RANGE radius $vector]=>{$YIELD_DISTANCE_AS: dist_field}" PARAMS 2 vector "binary_data" SORTBY dist_field DIALECT 2

FT.SEARCH idx:bikes_vss "@vector:[VECTOR_RANGE 0.5 $query_vector]=>{$YIELD_DISTANCE_AS: vector_dist}" PARAMS 2 "query_vector" "Z\xf8\x15:\xf23\xa1\xbfZ\x1dI>\r\xca9..." SORTBY vector_dist ASC RETURN 2 vector_dist description DIALECT 2

FT.SEARCH idx:bikes_vss "(*)=>[KNN 100 @vector $query_vector]=>{$SHARD_K_RATIO: 0.6; $YIELD_DISTANCE_AS: vector_distance}" PARAMS 2 "query_vector" "Z\xf8\x15:\xf23\xa1\xbfZ\x1dI>\r\xca9..." SORTBY vector_distance ASC RETURN 2 "vector_distance" "description" DIALECT 2

import assert from 'node:assert';
import fs from 'node:fs';
import { createClient } from 'redis';
import { SCHEMA_FIELD_TYPE, SCHEMA_VECTOR_FIELD_ALGORITHM } from '@redis/search';
import { pipeline } from '@xenova/transformers';

function float32Buffer(arr) {
  const floatArray = new Float32Array(arr);
  const float32Buffer = Buffer.from(floatArray.buffer);
  return float32Buffer;
}

async function embedText(sentence) {
  let modelName = 'Xenova/all-MiniLM-L6-v2';
  let pipe = await pipeline('feature-extraction', modelName);

  let vectorOutput = await pipe(sentence, {
      pooling: 'mean',
      normalize: true,
  });

  const embedding = Object.values(vectorOutput?.data);

  return embedding;
}

const vector_query = float32Buffer(await embedText('That is a very happy person'));

const client = createClient();
await client.connect().catch(console.error);

// create index
await client.ft.create('idx:bicycle', {
    '$.description': {
      type: SCHEMA_FIELD_TYPE.TEXT,
      AS: 'description'
    },
    '$.description_embeddings': {
        type: SCHEMA_FIELD_TYPE.VECTOR,
        TYPE: 'FLOAT32',
        ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.FLAT,
        DIM: 384,
        DISTANCE_METRIC: 'COSINE',
        AS: 'vector'
    }
}, {
    ON: 'JSON',
    PREFIX: 'bicycle:'
});

// load data
const bicycles = JSON.parse(fs.readFileSync('data/query_vector.json', 'utf8'));

await Promise.all(
  bicycles.map((bicycle, bid) => {
      return client.json.set(`bicycle:${bid}`, '$', bicycle);
  })
);

const res1 = await client.ft.search('idx:bicycle',
  '*=>[KNN 3 @vector $query_vector AS score]', {
    PARAMS: { query_vector: vector_query },
    RETURN: ['description'],
    DIALECT: 2
  }
);
console.log(res1.total); // >>> 3
console.log(res1); // >>>
//{
//  total: 3,
//  documents: [
//    { id: 'bicycle:0', value: [Object: null prototype] {} },
//    { id: 'bicycle:2', value: [Object: null prototype] {} },
//    { id: 'bicycle:9', value: [Object: null prototype] {} }
//  ]
//}

const res2 = await client.ft.search('idx:bicycle',
  '@vector:[VECTOR_RANGE 0.9 $query_vector]=>{$YIELD_DISTANCE_AS: vector_dist}', {
    PARAMS: { query_vector: vector_query },
    SORTBY: 'vector_dist',
    RETURN: ['vector_dist', 'description'],
    DIALECT: 2
  }
);
console.log(res2.total); // >>> 1
console.log(res2); // >>>
//{
//  total: 1,
//  documents: [ { id: 'bicycle:0', value: [Object: null prototype] } ]
//}

import json
import warnings
import redis
import numpy as np
from redisvl.index import SearchIndex
from redisvl.query import RangeQuery, VectorQuery
from redisvl.schema import IndexSchema
from sentence_transformers import SentenceTransformer

def embed_text(model, text):
    return np.array(model.encode(text)).astype(np.float32).tobytes()

r = redis.Redis(decode_responses=True)

warnings.filterwarnings("ignore", category=FutureWarning, message=r".*clean_up_tokenization_spaces.*")
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

# create index
schema = IndexSchema.from_yaml('data/query_vector_idx.yaml')
index = SearchIndex(schema, r)
index.create(overwrite=True, drop=True)

# load data
with open("data/query_vector.json") as f:
    bicycles = json.load(f)
index.load(bicycles)

query = "Bike for small kids"
query_vector = embed_text(model, query)
print(query_vector[:10]) # >>> b'\x02=c=\x93\x0e\xe0=aC'

vquery = VectorQuery(
    vector=query_vector,
    vector_field_name="description_embeddings",
    num_results=3,
    return_score=True,
    return_fields=["description"]
)
res = index.query(vquery)
print(res) # >>> [{'id': 'bicycle:6b702e8b...', 'vector_distance': '0.399111807346', 'description': 'Kids want...

vquery = RangeQuery(
    vector=query_vector,
    vector_field_name="description_embeddings",
    distance_threshold=0.5,
    return_score=True
).return_fields("description").dialect(2)
res = index.query(vquery)
print(res) # >>> [{'id': 'bicycle:6bcb1bb4...', 'vector_distance': '0.399111807346', 'description': 'Kids want...

FT.SEARCH idx:bikes_vss "(@brand:trek)=>[KNN 50 @vector $query_vector]=>{$SHARD_K_RATIO: 0.4; $YIELD_DISTANCE_AS: similarity}" PARAMS 2 "query_vector" "Z\xf8\x15:\xf23\xa1\xbfZ\x1dI>\r\xca9..." SORTBY similarity ASC RETURN 2 "similarity" "description" DIALECT 2


Vector search concepts

Summary: Learn how to use vector fields and perform vector searches in Redis

Summary: Vector quantization and compression for efficient memory usage and search performance

Vector quantization and compression

Overview

Redis includes a [high-performance vector database](https://redis.io/blog/benchmarking-results-for-vector-databases/) that lets you perform semantic searches over vector embeddings. You can augment these searches with filtering over text, numerical, geospatial, and tag metadata.

To quickly get started, check out the [Redis vector quickstart guide]() and the [Redis AI Resources](https://github.com/redis-developer/redis-ai-resources) Github repo.

Overview

1. [**Create a vector index**](): Redis maintains a secondary index over your data with a defined schema (including vector fields and metadata). Redis supports [`FLAT`](), [`HNSW`]() and [`SVS-VAMANA`]() vector index types.
1. [**Store and update vectors**](): Redis stores vectors and metadata in hashes or JSON objects.
1. [**Search with vectors**](): Redis supports several advanced querying strategies with vector fields including k-nearest neighbor ([KNN]()), [vector range queries](), and [metadata filters]().
1. [**Configure vector queries at runtime**]().
1. [**Vector search examples**](): Explore several vector search examples that cover different use cases and techniques.

Create a vector index

When you define the schema for an index, you can include one or more vector fields as shown below.

**Syntax**

[code example]

Refer to the full [indexing]() documentation for additional fields, options, and noted limitations.

**Parameters**

| Parameter | Description |
|:----------|:------------|
| `index_name`      | Name of the index.  |
| `storage_type`    | Storage option (`HASH` or `JSON`).  |
| `prefix` (optional) | Key prefix used to select which keys should be indexed. Defaults to all keys if omitted. |
| `field_name`      | Name of the vector field.  |
| `algorithm`       | Vector index algorithm (`FLAT` or `HNSW`).  |
| `index_attribute_count` | Number of vector field attributes.  |
| `index_attribute_name`  | Vector field attribute name.  |
| `index_attribute_value` | Vector field attribute value.  |

FLAT index

Choose the `FLAT` index when you have small datasets (< 1M vectors) or when perfect search accuracy is more important than search latency.

**Required attributes**

| Attribute          | Description                              |
|:-------------------|:-----------------------------------------|
| `TYPE`             | Vector type (`BFLOAT16`, `FLOAT16`, `FLOAT32`, `FLOAT64`, `INT8`, `UINT8`). `BFLOAT16` and `FLOAT16` require v2.10 or later, while `INT8` and `UINT8` require 8.0 or later. |
| `DIM`              | The width, or number of dimensions, of the vector embeddings stored in this field. In other words, the number of floating point elements comprising the vector. `DIM` must be a positive integer. The vector used to query this field must have the exact dimensions as the field itself.  |
| `DISTANCE_METRIC`  | Distance metric (`L2`, `IP`, `COSINE`).  |

**Example**

[code example]
In the example above, an index named `documents` is created over hashes with the key prefix `docs:` and a `FLAT` vector field named `doc_embedding` with three index attributes: `TYPE`, `DIM`, and `DISTANCE_METRIC`.

HNSW index

`HNSW`, or hierarchical navigable small world, is an approximate nearest neighbors algorithm that uses a multi-layered graph to make vector search more scalable.
- The lowest layer contains all data points, and each higher layer contains a subset, forming a hierarchy.
- At runtime, the search traverses the graph on each layer from top to bottom, finding the local minima before dropping to the subsequent layer.

Choose the `HNSW` index type when you have larger datasets (> 1M documents) or when search performance and scalability are more important than perfect search accuracy.

**Required attributes**

| Attribute          | Description                              |
|:-------------------|:-----------------------------------------|
| `TYPE`             | Vector type (`BFLOAT16`, `FLOAT16`, `FLOAT32`, `FLOAT64`, `INT8`, `UINT8`). `BFLOAT16` and `FLOAT16` require v2.10 or later, while `INT8` and `UINT8` require 8.0 or later. |
| `DIM`              | The width, or number of dimensions, of the vector embeddings stored in this field. In other words, the number of floating point elements comprising the vector. `DIM` must be a positive integer. The vector used to query this field must have the exact dimensions as the field itself. |
| `DISTANCE_METRIC`  | Distance metric (`L2`, `IP`, `COSINE`). |

**Optional attributes**

[`HNSW`](https://arxiv.org/ftp/arxiv/papers/1603/1603.09320.pdf) supports a number of additional parameters to tune
the accuracy of the queries, while trading off performance.

| Attribute          | Description                                                        | Default value |
|:-------------------|:-------------------------------------------------------------------|:-------------:|
| `M`                | Max number of outgoing edges (connections) for each node in a graph layer. On layer zero, the max number of connections will be `2 * M`. Higher values increase accuracy, but also increase memory usage and index build time. | 16 |
| `EF_CONSTRUCTION`  | Max number of connected neighbors to consider during graph building. Higher values increase accuracy, but also increase index build time. | 200 |
| `EF_RUNTIME`       | Max top candidates during KNN search. Higher values increase accuracy, but also increase search latency. | 10 |
| `EPSILON`          | Relative factor that sets the boundaries in which a range query may search for candidates. That is, vector candidates whose distance from the query vector is `radius * (1 + EPSILON)` are potentially scanned, allowing more extensive search and more accurate results, at the expense of run time. | 0.01 |

**Example**

[code example]

In the example above, an index named `documents` is created over hashes with the key prefix `docs:` and an `HNSW` vector field named `doc_embedding` with five index attributes: `TYPE`, `DIM`, `DISTANCE_METRIC`, `M`, and `EF_CONSTRUCTION`.

SVS-VAMANA index

Scalable Vector Search (SVS) is an Intel project featuring a graph-based vector search algorithm that is optimized to work with compression methods to reduce memory usage. You can read more about the project [here](https://intel.github.io/ScalableVectorSearch/intro.html). Support for SVS-VAMANA indexing was added in Redis 8.2.

Choose the `SVS-VAMANA` index type when all of the following requirements apply:
- High search performance and scalability are more important than exact search accuracy (similar to HNSW)
- Reduced memory usage
- Performance optimizations for Intel hardware

**Required attributes**

| Attribute          | Description                              |
|:-------------------|:-----------------------------------------|
| `TYPE`             | Vector type (`FLOAT16` or `FLOAT32`). Note: `COMPRESSION` is supported with both types. |
| `DIM`              | The width, or number of dimensions, of the vector embeddings stored in this field. In other words, the number of floating point elements comprising the vector. `DIM` must be a positive integer. The vector used to query this field must have the exact same dimensions as the field itself.  |
| `DISTANCE_METRIC`  | Distance metric (`L2`, `IP`, `COSINE`).  |

**Optional attributes**

`SVS-VAMANA` supports a number of additional parameters to tune the accuracy of the queries.

| Attribute                  | Description                              | Default value |
|:---------------------------|:-----------------------------------------|:-------------:|
| `COMPRESSION`              | Compression algorithm; one of `LVQ8`, `LVQ4`, `LVQ4x4`, `LVQ4x8`, `LeanVec4x8`, or `LeanVec8x8`. See [this page]() for more information. | none |
| `CONSTRUCTION_WINDOW_SIZE` | The search window size to use during graph construction. A higher search window size will yield a higher quality graph since more overall vertexes are considered, but will increase construction time. | 200 |
| `GRAPH_MAX_DEGREE`         | Sets the maximum number of edges per node; equivalent to `HNSW’s M*2`. A higher max degree may yield a higher quality graph in terms of recall for performance, but the memory footprint of the graph is directly proportional to the maximum degree. | 32 |
| `SEARCH_WINDOW_SIZE`       | The size of the search window; the same as `HSNW's EF_RUNTIME`. Increasing the search window size and capacity generally yields more accurate but slower search results. | 10 |
| `EPSILON`                  | The range search approximation factor; the same as `HSNW's EPSILON`. | 0.01 |
| `TRAINING_THRESHOLD`       | Number of vectors needed to learn compression parameters. Applicable only when used with `COMPRESSION`. Increase if recall is low. Note: setting this too high may slow down search.If a value is provided, it must be less than `100 * DEFAULT_BLOCK_SIZE`, where `DEFAULT_BLOCK_SIZE` is 1024. | `10 * DEFAULT_BLOCK_SIZE` |
| `REDUCE`              | The dimension used when using `LeanVec4x8` or `LeanVec8x8` compression for dimensionality reduction. If a value is provided, it should be less than `DIM`. Lowering it can speed up search and reduce memory use. | `DIM / 2` |

Some advanced vector compression features may depend on hardware or Intel's proprietary optimizations. Intel's proprietary LVQ and LeanVec optimizations are not available in Redis Open Source. On non-Intel platforms and Redis Open Source platforms, `SVS-VAMANA` with `COMPRESSION` will fall back to basic, 8-bit scalar quantization implementation: all values in a vector are scaled using the global minimum and maximum, and then each dimension is quantized independently into 256 levels using 8-bit precision.

**Example**

[code example]

In the example above, an index named `documents` is created over hashes with the key prefix `docs:` and an `SVS-VAMANA` vector field named `doc_embedding` with six index attributes: `TYPE`, `DIM`, `DISTANCE_METRIC`, `GRAPH_MAX_DEGREE`, `CONSTRUCTION_WINDOW_SIZE` and `COMPRESSION`.

Distance metrics

Redis supports three popular distance metrics to measure the degree of similarity between two vectors $u$, $v$ $\in \mathbb{R}^n$, where $n$ is the length of the vectors:

| Distance metric | Description | Mathematical representation |
|:--------------- |:----------- |:--------------------------- |
| `L2` | Euclidean distance between two vectors. | $d(u, v) = \sqrt{ \displaystyle\sum_{i=1}^n{(u_i - v_i)^2}}$ |
| `IP` | Inner product of two vectors. | $d(u, v) = 1 -u\cdot v$ |
| `COSINE` | Cosine distance of two vectors. | $d(u, v) = 1 -\frac{u \cdot v}{\lVert u \rVert \lVert v  \rVert}$ |

The above metrics calculate distance between two vectors, where the smaller the value is, the closer the two vectors are in the vector space.

Store and update vectors

On index creation, the `<storage_type>` dictates how vector and metadata are structured and loaded into Redis.

Hash

Store or update vectors and any metadata in [hashes]() using the [`HSET`]() command.

**Example**

[code example]

Hash values are stored as binary-safe strings. The value `<vector_bytes>` represents the vector's underlying memory buffer.

A common method for converting vectors to bytes uses the [redis-py](https://redis.readthedocs.io/en/stable/examples/search_vector_similarity_examples.html) client library and the Python [NumPy](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.tobytes.html) library.

**Example**

[code example]

The vector blob size must match the dimension and float type of the vector field specified in the index's schema; otherwise, indexing will fail.

JSON

You can store or update vectors and any associated metadata in [JSON]() using the [`JSON.SET`]() command.

To store vectors in Redis as JSON, you store the vector as a JSON array of floats. Note that this differs from vector storage in Redis hashes, which are instead stored as raw bytes.

**Example**

[code example]

One of the benefits of JSON is schema flexibility. As of v2.6.1, JSON supports multi-value indexing.
This allows you to index multiple vectors under the same [JSONPath]().

Here are some examples of multi-value indexing with vectors:

**Multi-value indexing example**

[code example]

Additional information and examples are available in the [Indexing JSON documents]() section.

Search with vectors

You can run vector search queries with the [`FT.SEARCH`]() or [`FT.AGGREGATE`]() commands.

To issue a vector search query with `FT.SEARCH`, you must set the `DIALECT` option to >= `2`. See the [dialects documentation]() for more information.

KNN vector search

KNN vector search finds the top k nearest neighbors to a query vector. It has the following syntax:

**Syntax**

[code example]
**Parameters**

| Parameter         | Description                                                                                |
|:------------------|:--------------------------------------------------------------------------------------------------|
| `index_name`  | Name of the index.  |
| `primary_filter_query`  | [Filter]() criteria. Use `*` when no filters are required.  |
| `top_k` | Number of nearest neighbors to fetch from the index.  |
| `vector_field`  | Name of the vector field to search against.  |
| `vector_blob_param`  | The query vector, passed in as a blob of raw bytes. The blob's byte size must match the vector field's dimensions and type.  |
| `vector_query_params` (optional) | An optional section for marking one or more vector query parameters passed through the `PARAMS` section. Valid parameters should be provided as key-value pairs. See which [runtime query params]() are supported for each vector index type.  |
| `distance_field` (optional) | The optional distance field name used in the response and/or for sorting. By default, the distance field name is `__<vector_field>_score` and it can be used for sorting without using `AS <distance_field>` in the query.  |
| `vector_query_params_count` | The number of vector query parameters.  |
| `vector_query_param_name` | The name of the vector query parameter.  |
| `vector_query_param_value` | The value of the vector query parameter.  |

**Example**

[code example]

**Use query attributes**

Alternatively, as of v2.6, `<vector_query_params>` and `<distance_field>` name can be specified in runtime
[query attributes]() as shown below.

[code example]

Vector range queries

Vector range queries allow you to filter the index using a `radius` parameter representing the semantic distance between an input query vector and indexed vector fields. This is useful in scenarios when you don't know exactly how many nearest (`top_k`) neighbors to fetch, but you do know how similar the results should be.

For example, imagine a fraud or anomaly detection scenario where you aren't sure if there are any matches in the vector index. You can issue a vector range query to quickly check if there are any records of interest in the index within the specified radius.

Vector range queries operate slightly different than KNN vector queries:
- Vector range queries can appear multiple times in a query as filter criteria.
- Vector range queries can be a part of the `<primary_filter_query>` in KNN vector search.

**Syntax**

[code example]

| Parameter         | Description                                                                                |
|:------------------|:--------------------------------------------------------------------------------------------------|
| `index_name`  | Name of the index.  |
| `vector_field`  | Name of the vector field in the index. |
| `radius` or `radius_param` | The maximum semantic distance allowed between the query vector and indexed vectors. You can provide the value directly in the query, passed to the `PARAMS` section, or as a query attribute.
| `vector_blob_param`  | The query vector, passed in as a blob of raw bytes. The blob's byte size must match the vector field's dimensions and type. |
| `vector_query_params` (optional) | An optional section for marking one or more vector query parameters passed through the `PARAMS` section. Valid parameters should be provided as key-value pairs. See which [runtime query params]() are supported for each vector index type.  |
| `vector_query_params_count` | The number of vector query parameters.  |
| `vector_query_param_name` | The name of the vector query parameter.  |
| `vector_query_param_value` | The value of the vector query parameter.  |

**Use query attributes**

A vector range query clause can be followed by a query attributes section as follows:

[code example]

where the relevant parameters in that case are `$yield_distance_as` and `$epsilon`. Note that there is no default distance field name in range queries.

Filters

Redis supports vector searches that include filters to narrow the search space based on defined criteria. If your index contains searchable fields (for example, `TEXT`, `TAG`, `NUMERIC`, `GEO`, `GEOSHAPE`, and `VECTOR`), you can perform vector searches with filters.

**Supported filter types**

- [Exact match](https://redis.io/docs/develop/ai/search-and-query/query/exact-match/)
- [Numeric range](https://redis.io/docs/develop/ai/search-and-query/query/range/)
- [Full-text](https://redis.io/docs/develop/ai/search-and-query/query/full-text/)
- [Geospatial](https://redis.io/docs/develop/ai/search-and-query/query/geo-spatial/)

You can also [combine multiple queries](https://redis.io/docs/develop/ai/search-and-query/query/combined/) as a filter.

**Syntax**

Vector search queries with filters follow this basic structure:

[code example]

where `<primary_filter_query>` defines document selection and filtering.

**Example**

[code example]

How filtering works

Redis uses internal algorithms to optimize the filtering computation for vector search.
The runtime algorithm is determined by heuristics that aim to minimize query latency based on several factors derived from the query and the index.

**Batches mode**

Batches mode works by paginating through small batches of nearest neighbors from the index:
- A batch of high-scoring documents from the vector index is retrieved. These documents are yielded only if the `<primary_filter_query>` is satisfied. In other words, the document must contain a similar vector and meet the filter criteria.
- The iterative procedure terminates when `<top_k>` documents that pass the filter criteria are yielded, or after every vector in the index has been processed.
- The batch size is determined automatically by heuristics based on `<top_k>` and the ratio between the expected number of documents in the index that pass the `<primary_filter_query>` and the vector index size.
- The goal is to minimize the total number of batches required to get the `<top_k>` results while preserving the smallest batch size possible. Note that the batch size may change dynamically in each iteration based on the number of results that pass the filter in previous batches.

**Ad-hoc brute force mode**

- The score of every vector corresponding to a document that passes the filter is computed, and the `<top_k>` results are selected and returned.
- This approach is preferable when the number of documents passing the `<primary_filter_query>` is relatively small.
- The results of the KNN query will always be accurate in this mode, even if the underlying vector index algorithm is an approximate one.

The execution mode may switch from batch mode to ad-hoc brute-force mode during the run, based on updated estimations of relevant factors from one batch to another.

Runtime query parameters

Filter mode

By default, Redis selects the best filter mode to optimize query execution. You can override the auto-selected policy using these optional parameters:

| Parameter        | Description | Options |
|:-----------------|:------------|:--------|
| `HYBRID_POLICY`  | Specifies the filter mode to use during vector search with filters (hybrid). | `BATCHES` or `ADHOC_BF` |
| `BATCH_SIZE`     | A fixed batch size to use in every iteration when the `BATCHES` policy is auto-selected or requested. | Positive integer. |

Cluster-specific query parameters

**$SHARD_K_RATIO**

The `$SHARD_K_RATIO` parameter controls how many results each shard retrieves relative to the requested `top_k` in Redis cluster environments. This creates a tunable trade-off between accuracy and performance.

| Parameter        | Description | Value Range | Default |
|:-----------------|:------------|:------------|:--------|
| `$SHARD_K_RATIO` | Ratio of `top_k` results to fetch per shard in cluster setups. Calculation: `shard_results = top_k × ratio`. | 0.1 - 1.0 (up to 2 decimal places) | 1.0 |

**Important considerations:**

- Only applicable to vector KNN queries in Redis cluster environments
- Each shard must retrieve at least `max(top_k / #shards, ceil(top_k × ratio))` results
- If the user-defined ratio is lower than `top_k / #shards`, the server ignores it and uses the minimum required ratio
- Unbalanced shards returning fewer results than required may lead to fewer total results than requested in `top_k`
- Invalid ratios return descriptive error messages

Index-specific query parameters

**FLAT**

Currently, there are no runtime parameters available for FLAT indexes.

**HNSW**

Optional runtime parameters for HNSW indexes are:

| Parameter       | Description                                                                                | Default value       |
|:----------------|:----------------------------------------------------------------------------------------------------------|:--------------------|
| `EF_RUNTIME`    | The maximum number of top candidates to hold during the KNN search. Higher values lead to more accurate results at the expense of a longer query runtime. | The value passed during index creation. The default is 10. |
| `EPSILON`       | The relative factor that sets the boundaries for a vector range query. Vector candidates whose distance from the query vector is `radius * (1 + EPSILON)` are potentially scanned, allowing a more extensive search and more accurate results at the expense of runtime. | The value passed during index creation. The default is 0.01. |

**SVS-VAMANA**

Optional runtime parameters for SVS-VAMANA indexes are:

| Parameter | Description | Default value |
|:----------|:------------|:--------------|
| `SEARCH_WINDOW_SIZE` | The size of the search window (applies only to KNN searches). | 10 or the value that was passed upon index creation. |
| `EPSILON` | The range search approximation factor. | 0.01 or the value that was passed upon index creation. |
| `USE_SEARCH_HISTORY` | When building an index, either the contents of the search buffer is used or the entire search history is used. The latter case may yield a slightly better graph at the cost of more search time. Boolean options are `OFF`, `ON`, and `AUTO`. `AUTO` is always evaluated internally as `ON`. | `AUTO` |
| `SEARCH_BUFFER_CAPACITY` | A tuning parameter used in compressed `SVS-VAMANA` indexes, which are using a two-level compression type (`LVQ<X>x<Y>` or one of the `LeanVec` types), that determines the number of vector candidates to collect in the first level of the search, before the re-ranking level (which is the second level). | `SEARCH_WINDOW_SIZE` |

Important notes

1. When performing a KNN vector search, you specify `<top_k>` nearest neighbors. However, the default Redis query `LIMIT` parameter (used for pagination) is 10. In order to get `<top_k>` returned results, you must also specify `LIMIT 0 <top_k>` in your search command. See examples below.

2. By default, the results are sorted by their document's score. To sort by vector similarity score, use `SORTBY <distance_field>`. See examples below.

3. Depending on your chosen distance metric, the calculated distance between vectors in an index have different bounds. For example, `Cosine` distance is bounded by `2`, while `L2` distance is not bounded. When performing a vector range query, the best practice is to adjust the `<radius>` parameter based on your use case and required recall or precision metrics.

Vector search examples

Below are a number of examples to help you get started. For more comprehensive walkthroughs, see the [Redis vector quickstart guide]() and the [Redis AI Resources](https://github.com/redis-developer/redis-ai-resources) Github repo.

KNN vector search examples

Return the 10 nearest neighbor documents for which the `doc_embedding` vector field is the closest to the query vector represented by the following 4-byte blob:

[code example]

Return the top 10 nearest neighbors and customize the `K` and `EF_RUNTIME` parameters using query parameters. See the "Optional arguments" section in [FT.SEARCH command](). Set the `EF_RUNTIME` value to 150, assuming `doc_embedding` is an `HNSW` index:

[code example]

Assign a custom name to the distance field (`vector_distance`) and then sort using that name:

[code example]

Use [query attributes]() syntax to specify optional parameters and the distance field name:

[code example]

To explore additional Python vector search examples, review recipes for the [`Redis Python`](https://github.com/redis-developer/redis-ai-resources/blob/main/python-recipes/vector-search/00_redispy.ipynb) client library and the [`Redis Vector Library`](https://github.com/redis-developer/redis-ai-resources/blob/main/python-recipes/vector-search/01_redisvl.ipynb).

Filter examples

For these examples, assume you created an index named `movies` with records of different movies and their metadata.

Among the movies that have `'Dune'` in the `title` field and `year` between `[2020, 2022]`, return the top 10 nearest neighbors, sorted by `movie_distance`:

[code example]

Among the movies that have `action` as a category tag, but not `drama`, return the top 10 nearest neighbors, sorted by `movie_distance`:

[code example]

Among the movies that have `drama` or `action` as a category tag, return the top 10 nearest neighbors and explicitly set the filter mode (hybrid policy) to "ad-hoc brute force" rather than it being auto-selected:

[code example]

Among the movies that have `action` as a category tag, return the top 10 nearest neighbors and explicitly set the filter mode (hybrid policy) to "batches" and batch size 50 using a query parameter:

[code example]

Run the same query as above and use the query attributes syntax to specify optional parameters:

[code example]

To explore additional Python vector search examples, review recipes for the [`Redis Python`](https://github.com/redis-developer/redis-ai-resources/blob/main/python-recipes/vector-search/00_redispy.ipynb) client library and the [`Redis Vector Library`](https://github.com/redis-developer/redis-ai-resources/blob/main/python-recipes/vector-search/01_redisvl.ipynb).

Cluster optimization examples

The following examples demonstrate using `$SHARD_K_RATIO` to optimize vector search performance in Redis cluster environments.

Return the top 100 nearest neighbors with each shard providing 50% of the requested results (50 results per shard):

[code example]

Combine cluster optimization with filtering and other query attributes. Among movies with `action` category, return top 50 nearest neighbors with 30% shard ratio and custom EF_RUNTIME:

[code example]

Use a higher ratio for better accuracy when precision is more important than performance:

[code example]

Cluster considerations and best practices

When using `$SHARD_K_RATIO` in Redis cluster environments, consider the following behavioral characteristics and best practices:

#### Minimum results guarantee

Each shard must retrieve at least `max(top_k / #shards, ceil(top_k × ratio))` results to ensure the cluster can return the requested `top_k` results. If your specified ratio would result in fewer results per shard than this minimum, Redis automatically adjusts to the minimum required ratio.

**Example scenarios:**
- `top_k=100`, 4 shards, `ratio=0.5` → 50 results per shard (valid)
- `top_k=100`, 4 shards, `ratio=0.2` → 25 results per shard (minimum: 25, so valid)
- `top_k=100`, 8 shards, `ratio=0.1` → 10 results per shard (minimum: 13, so Redis uses 13)

#### Handling unbalanced shards

In cases where some shards contain fewer documents than required to fulfill the `top_k` results, using a lower ratio may lead to fewer total results than requested. To mitigate this:

1. **Monitor shard distribution**: Ensure your data is relatively balanced across shards
2. **Adjust ratio dynamically**: If you consistently get fewer results than expected, increase the ratio
3. **Use higher ratios for critical queries**: Do this when result completeness is more important than performance

#### Performance vs accuracy trade-offs

| Ratio Range | Use Case | Performance | Accuracy |
|:------------|:---------|:------------|:---------|
| 0.1 - 0.3   | High-performance, approximate results | Excellent | Good |
| 0.4 - 0.6   | Balanced performance and accuracy | Good | Very Good |
| 0.7 - 1.0   | High-accuracy, precision-critical | Fair | Excellent |

#### Error handling

Redis returns descriptive error messages for invalid `$SHARD_K_RATIO` values:
- Values outside the 0.1 - 1.0 range
- Values with more than 2 decimal places
- Non-numeric values

Range query examples

For these examples, assume you created an index named `products` with records of different products and metadata from an ecommerce site.

Return 100 products for which the distance between the `description_vector` field and the specified query vector blob is at most 5:

[code example]

Run the same query as above and set the `EPSILON` parameter to `0.5`, assuming `description_vector` is HNSW index, yield the vector distance between `description_vector` and the query result in a field named `vector_distance`, and sort the results by that distance.

[code example]

Use the vector range query as a filter: return all the documents that contain either `'shirt'` in their `type` tag with their `year` value in the range `[2020, 2022]` or a vector stored in `description_vector` whose distance from the query vector is no more than `0.8`, then sort the results by their vector distance, if it is in the range:

[code example]

To explore additional Python vector search examples, review recipes for the [`Redis Python`](https://github.com/redis-developer/redis-ai-resources/blob/main/python-recipes/vector-search/00_redispy.ipynb) client library and the [`Redis Vector Library`](https://github.com/redis-developer/redis-ai-resources/blob/main/python-recipes/vector-search/01_redisvl.ipynb).

Memory consumption comparison

Following is a Python+NumPy example of vector sizes for the supported vector types; `BFLOAT16`, `FLOAT16`, `FLOAT32`, and `FLOAT64`.

[code example]

Next steps

Vector embeddings and vector search are not new concepts. Many of the largest companies have used
vectors to represent products in ecommerce catalogs or content in advertising pipelines for well over a decade.

With the emergence of Large Language Models (LLMs) and the proliferation of applications that require advanced information
retrieval techniques, Redis is well positioned to serve as your high performance query engine for semantic search and more.

Here are some additional resources that apply vector search for different use cases:

- [Retrieval augmented generation from scratch](https://github.com/redis-developer/redis-ai-resources/blob/main/python-recipes/RAG/01_redisvl.ipynb)
- [Semantic caching](https://github.com/redis-developer/redis-ai-resources/blob/main/python-recipes/semantic-cache/00_semantic_caching_gemini.ipynb)

Continue learning with Redis University

FT.CREATE <index_name>
  ON <storage_type>
  PREFIX 1 <key_prefix>
  SCHEMA ... <field_name> VECTOR <algorithm> <index_attribute_count> <index_attribute_name> <index_attribute_value>
    [<index_attribute_name> <index_attribute_value> ...]

FT.CREATE documents
  ON HASH
  PREFIX 1 docs:
  SCHEMA doc_embedding VECTOR FLAT 6
    TYPE FLOAT32
    DIM 1536
    DISTANCE_METRIC COSINE

FT.CREATE documents
  ON HASH
  PREFIX 1 docs:
  SCHEMA doc_embedding VECTOR HNSW 10
    TYPE FLOAT64
    DIM 1536
    DISTANCE_METRIC COSINE
    M 40
    EF_CONSTRUCTION 250

FT.CREATE documents
  ON HASH
  PREFIX 1 docs:
  SCHEMA doc_embedding VECTOR SVS-VAMANA 12
    TYPE FLOAT32
    DIM 1536
    DISTANCE_METRIC COSINE
    GRAPH_MAX_DEGREE 40
    CONSTRUCTION_WINDOW_SIZE 250
    COMPRESSION LVQ8

HSET docs:01 doc_embedding <vector_bytes> category sports

import numpy as np
from redis import Redis

redis_client = Redis(host='localhost', port=6379)

# Create a FLOAT32 vector
vector = np.array([0.34, 0.63, -0.54, -0.69, 0.98, 0.61], dtype=np.float32)

# Convert vector to bytes
vector_bytes = vector.tobytes()

# Use the Redis client to store the vector bytes and metadata at a specified key
redis_client.hset('docs:01', mapping = {"vector": vector_bytes, "category": "sports"})

FT.SEARCH <index_name>
  <primary_filter_query>=>[KNN <top_k> @<vector_field> $<vector_blob_param> $<vector_query_params> AS <distance_field>]
  PARAMS <query_params_count> [$<vector_blob_param> <vector_blob> <query_param_name> <query_param_value> ...]
  SORTBY <distance_field>
  DIALECT 2

FT.SEARCH documents "*=>[KNN 10 @doc_embedding $BLOB]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" DIALECT 2

[KNN <top_k> @<vector_field> $<vector_blob_param>]=>{$yield_distance_as: <distance_field>}

FT.SEARCH <index_name>
  @<vector_field>:[VECTOR_RANGE (<radius> | $<radius_param>) $<vector_blob_param> $<vector_query_params>]
  PARAMS <vector_query_params_count> [<vector_query_param_name> <vector_query_param_value> ...]
  SORTBY <distance_field>
  DIALECT 2

@<vector_field>: [VECTOR_RANGE (<radius> | $<radius_param>) $<vector_blob_param>]=>{$<param>: (<value> |
    $<value_attribute>); ... }

FT.SEARCH documents "(@title:Sports @year:[2020 2022])=>[KNN 10 @doc_embedding $BLOB]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" DIALECT 2

FT.SEARCH documents "*=>[KNN 10 @doc_embedding $BLOB]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY __vector_score DIALECT 2

FT.SEARCH documents "*=>[KNN $K @doc_embedding $BLOB EF_RUNTIME $EF]" PARAMS 6 BLOB "\x12\xa9\xf5\x6c" K 10 EF 150 DIALECT 2

FT.SEARCH documents "*=>[KNN 10 @doc_embedding $BLOB AS vector_distance]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY vector_distance DIALECT 2

FT.SEARCH documents "*=>[KNN 10 @doc_embedding $BLOB]=>{$EF_RUNTIME: $EF; $YIELD_DISTANCE_AS: vector_distance}" PARAMS 4 EF 150 BLOB "\x12\xa9\xf5\x6c" SORTBY vector_distance DIALECT 2

FT.SEARCH movies "(@title:Dune @year:[2020 2022])=>[KNN 10 @movie_embedding $BLOB AS movie_distance]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY movie_distance DIALECT 2

FT.SEARCH movies "(@category:{action} ~@category:{drama})=>[KNN 10 @doc_embedding $BLOB AS movie_distance]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY movie_distance DIALECT 2

FT.SEARCH movies "(@category:{drama | action})=>[KNN 10 @doc_embedding $BLOB HYBRID_POLICY ADHOC_BF]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY __vec_score DIALECT 2

FT.SEARCH movies "(@category:{action})=>[KNN 10 @doc_embedding $BLOB HYBRID_POLICY BATCHES BATCH_SIZE $BATCH_SIZE]" PARAMS 4 BLOB "\x12\xa9\xf5\x6c" BATCH_SIZE 50 DIALECT 2

FT.SEARCH movies "(@category:{action})=>[KNN 10 @doc_embedding $BLOB]=>{$HYBRID_POLICY: BATCHES; $BATCH_SIZE: 50}" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" DIALECT 2

FT.SEARCH documents "*=>[KNN 100 @doc_embedding $BLOB]=>{$SHARD_K_RATIO: 0.5; $YIELD_DISTANCE_AS: vector_distance}" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY vector_distance DIALECT 2

FT.SEARCH movies "(@category:{action})=>[KNN 50 @movie_embedding $BLOB]=>{$SHARD_K_RATIO: 0.3; $EF_RUNTIME: 150; $YIELD_DISTANCE_AS: movie_distance}" PARAMS 4 BLOB "\x12\xa9\xf5\x6c" EF 150 SORTBY movie_distance DIALECT 2

FT.SEARCH products "*=>[KNN 20 @product_embedding $BLOB]=>{$SHARD_K_RATIO: 0.8; $YIELD_DISTANCE_AS: similarity}" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY similarity DIALECT 2

FT.SEARCH products "@description_vector:[VECTOR_RANGE 5 $BLOB]" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" LIMIT 0 100 DIALECT 2

FT.SEARCH products "@description_vector:[VECTOR_RANGE 5 $BLOB]=>{$EPSILON:0.5; $YIELD_DISTANCE_AS: vector_distance}" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY vector_distance LIMIT 0 100 DIALECT 2

FT.SEARCH products "(@type:{shirt} @year:[2020 2022]) | @description_vector:[VECTOR_RANGE 0.8 $BLOB]=>{$YIELD_DISTANCE_AS: vector_distance}" PARAMS 2 BLOB "\x12\xa9\xf5\x6c" SORTBY vector_distance DIALECT 2

import numpy as np

#install ml_dtypes from pip install ml-dtypes
from ml_dtypes import bfloat16

# random float64 100 dimensions
double_precision_vec = np.random.rand(100)

# for float64 and float32
print(f'length of float64 vector: {len(double_precision_vec.tobytes())}') # >>> 800
print(f'length of float32 vector: {len(double_precision_vec.astype(np.float32).tobytes())}') # >>> 400

# for float16
np_data_type = np.float16
half_precision_vec_float16 = double_precision_vec.astype(np_data_type)
print(f'length of float16 vector: {len(half_precision_vec_float16.tobytes())}') # >>> 200

# for bfloat16
bfloat_dtype = bfloat16
half_precision_vec_bfloat16 = double_precision_vec.astype(bfloat_dtype)
print(f'length of bfloat16 vector: {len(half_precision_vec_bfloat16.tobytes())}') # >>> 200


Vector quantization and compression

Overview

Efficient management of high-dimensional vector data is crucial for scalable search and retrieval. Advanced methods for vector quantization and compression, such as LVQ (Locally-adaptive Vector Quantization) and LeanVec, can dramatically optimize memory usage and improve search speed, without sacrificing much accuracy. This page describes practical approaches to quantizing and compressing vectors for scalable search.

Some advanced vector compression features may depend on hardware or Intel's proprietary optimizations. Intel's proprietary LVQ and LeanVec optimizations are not available in Redis Open Source. On non-Intel platforms and Redis Open Source platforms, `SVS-VAMANA` with `COMPRESSION` will fall back to basic, 8-bit scalar quantization implementation: all values in a vector are scaled using the global minimum and maximum, and then each dimension is quantized independently into 256 levels using 8-bit precision.

Quantization and compression techniques

* **Method:** Applies per-vector normalization and scalar quantization; learns parameters directly from the data.
* **Advantages:**
    * Enables fast, on-the-fly distance computations.
    * SIMD-optimized layout for efficient search.
    * Learns compression parameters from representative vectors.
* **Variants:**
    * **LVQ4x4:** 8 bits per dimension, fast search, large memory savings.
    * **LVQ8:** Faster ingestion, slower search.
    * **LVQ4x8:** Two-level quantization for improved recall.

LeanVec (LVQ with dimensionality reduction)

* **Method:** Combines dimensionality reduction with LVQ, applying quantization after reducing vector dimensions.
* **Advantages:**
    * Best suited for high-dimensional vectors.
    * Significant speed and memory improvements.
* **Variants:**
    * **LeanVec4x8:** Recommended for high-dimensional datasets, fastest search and ingestion.
    * **LeanVec8x8:** Improved recall when more granularity is needed.
* **LeanVec Dimension:** For faster search and lower memory usage, reduce the dimension further by using the optional `REDUCE` argument. The default is typically `input dimension / 2`, but more aggressive reduction (such as `input dimension / 4`) is possible for greater efficiency.

Choosing a compression type

| Compression type     | Best for                                         | Observations                                            |
|----------------------|--------------------------------------------------|---------------------------------------------------------|
| LVQ4x4               | Fast search and low memory use                   | Consider LeanVec for even faster search                 |
| LeanVec4x8           | Fastest search and ingestion                     | LeanVec dimensionality reduction might reduce recall    |
| LVQ4                 | Maximum memory saving                            | Recall might be insufficient                            |
| LVQ8                 | Faster ingestion than LVQ4x4                     | Search likely slower than LVQ4x4                        |
| LeanVec8x8           | Improved recall when LeanVec4x8 is insufficient  | LeanVec dimensionality reduction might reduce recall    |
| LVQ4x8               | Improved recall when LVQ4x4 is insufficient      | Slightly worse memory savings                           |

Two-level compression

Both LVQ and LeanVec support two-level compression schemes. LVQ's two-level compression works by first quantizing each vector individually to capture its main structure, then encoding the residual error—the difference between the original and quantized vector—using a second quantization step. This allows fast search using only the first level, with the second level used for re-ranking to boost accuracy when needed.

Similarly, LeanVec uses a two-level approach: the first level reduces dimensionality and applies LVQ to speed up candidate retrieval, while the second level applies LVQ to the original high-dimensional vectors for accurate re-ranking.

Note that the original full-precision embeddings are never used by either LVQ or LeanVec, as both operate entirely on compressed representations.

This two-level approach allows for:

* Fast candidate retrieval using the first-level compressed vectors.
* High-accuracy re-ranking using the second-level residuals.

The naming convention used for the configurations reflects the number of bits allocated per dimension at each level of compression.

Naming convention: LVQ<B₁>x<B₂>

* **B₁:** Number of bits per dimension used in the first-level quantization.
* **B₂:** Number of bits per dimension used in the second-level quantization (residual encoding).

#### Examples

* **LVQ4x8:**
    * First level: 4 bits per dimension.
    * Second level: 8 bits per dimension.
    * Total: 12 bits per dimension (used across two stages).
* **LVQ8:**
    * Single-level compression only.
    * 8 bits per dimension.
    * No second-level residuals.

Same notation is used for LeanVec.

Learning compression parameters from vector data

The strong performance of LVQ and LeanVec stems from their ability to adapt to the structure of the input vectors. By learning compression parameters directly from the data, they achieve more accurate representations with fewer bits.

What does this mean in practice?

* **Initial training requirement:**
    A minimum number of representative vectors is required during index initialization to train the compression parameters (see the [TRAINING_THRESHOLD]() parameter). A random sample from the dataset typically works well.
* **Handling data drift:**
    If the characteristics of incoming vectors change significantly over time (that is, a data distribution shift), compression quality may degrade. This is a general limitation of all data-dependent compression methods,not just LVQ and LeanVec. When the data no longer resembles the original training sample, the learned representation becomes less effective.

Build Redis Open Source with Intel SVS support

By default, Redis Open Source with Redis Search supports SVS-VAMANA indexing with the global 8-bit quantisation. To compile Redis with the Intel SVS-VAMANA optimisations, LeanVec and LVQ, for Intel platforms, follow the instructions below.

If you are using Redis Open Source under the AGPLv3 or SSPLv1 licenses, you cannot use it together with the Intel Optimization binaries (LeanVec and LVQ). The reason is that the Intel SVS license is not compatible with those licenses.
The LeanVec and LVQ techniques are closed source and are only available for use with Redis Open Source when distributed under the RSALv2 license.
For more details, please refer to the [information provided by Intel](https://github.com/intel/ScalableVectorSearch).

Build Redis Open Source

Follow the [Redis Open Source build instructions](). Before executing `make`, define the following environment variable.

[code example]

Alternatively, you can define the `BUILD_INTEL_SVS_OPT` variable as part of the `make` command:

[code example]


Client-side caching introduction

Summary: Server-assisted, client-side caching in Redis

Overview

*Client-side caching* reduces network traffic between
a Redis client and the server, which generally improves performance.

By default, an [application server](https://en.wikipedia.org/wiki/Application_server)
(which sits between the user app and the database) contacts the
Redis database server through the client library for every read request.
The diagram below shows the flow of communication from the user app,
through the application server to the database and back again:

When you use client-side caching, the client library
maintains a local cache of data items as it retrieves them
from the database. When the same items are needed again, the client
can satisfy the read requests from the cache instead of the database:

Accessing the cache is much faster than communicating with the database over the
network and it reduces network traffic. Client-side caching reduces
the load on the database server, so you may be able to run it using less hardware
resources.

As with other forms of [caching](https://en.wikipedia.org/wiki/Cache_(computing)),
client-side caching works well in the very common use case where a small subset of the data
is accessed much more frequently than the rest of the data (according
to the [Pareto principle](https://en.wikipedia.org/wiki/Pareto_principle)).

Updating the cache when the data changes {#tracking}

All caching systems must implement a scheme to update data in the cache
when the corresponding data changes in the main database. Redis uses an
approach called *tracking*.

When client-side caching is enabled, the Redis server remembers or *tracks* the set of keys
that each client connection has previously read. This includes cases where the client
reads data directly, as with the [`GET`]()
command, and also where the server calculates values from the stored data,
as with [`STRLEN`](). When any client
writes new data to a tracked key, the server sends an invalidation message
to all clients that have accessed that key previously. This message warns
the clients that their cached copies of the data are no longer valid and the clients
will evict the stale data in response. Next time a client reads from
the same key, it will access the database directly and refresh its cache
with the updated data.

If any connection from a client gets disconnected (including
one from a connection pool), then the client will flush all keys from the
client-side cache. Caching then resumes for subsequent reads from the
connections that are still active.

The sequence diagram below shows how two clients might interact as they
access and update the same key:

Which client libraries support client-side caching?

The following client libraries support CSC from the stated version onwards:

| Client | Version |
| :-- | :-- |
| [`redis-py`]() | v5.1.0 |
| [`Jedis`]() | v5.2.0 |
| [`node-redis`]() | v5.1.0 |

Which commands can cache data?

All read-only commands (with the `@read`
[ACL category]())
will use cached data, except for the following:

-   Any commands for the
    [probabilistic]() and
    [time series]() data types.
    These types are designed to be updated frequently, which means that caching
    has little or no benefit.
-   Non-deterministic commands such as [`HRANDFIELD`](),
    [`HSCAN`](),
    and [`ZRANDMEMBER`](). By design, these commands
    give different results each time they are called.
-   Redis Search commands (with the `FT.*` prefix), such as
    [`FT.SEARCH`]().

You can use the [`MONITOR`]() command to
check the server's behavior when you are using client-side caching. Because `MONITOR` only
reports activity from the server, you should find the first cacheable
access to a key causes a response from the server. However, subsequent
accesses are satisfied by the cache, and so `MONITOR` should report no
server activity if client-side caching is working correctly.

What data gets cached for a command?

Broadly speaking, the data from the specific response to a command invocation
gets cached after it is used for the first time. Subsets of that data
or values calculated from it are retrieved from the server as usual and
then cached separately. For example:

-   The whole string retrieved by [`GET`]()
    is added to the cache. Parts of the same string retrieved by
    [`SUBSTR`]() are calculated on the
    server the first time and then cached separately from the original
    string.
-   Using [`GETBIT`]() or
    [`BITFIELD`]() on a string
    caches the returned values separately from the original string.
-   For composite data types accessed by keys
    ([hash](),
    [JSON](),
    [set](), and
    [sorted set]()),
    the whole object is cached separately from the individual fields.
    So the results of `JSON.GET mykey $` and `JSON.GET mykey $.myfield` create
    separate entries in the cache.
-   Ranges from [lists](),
    [streams](),
    and [sorted sets]()
    are cached separately from the object they form a part of. Likewise,
    subsets returned by [`SINTER`]() and
    [`SDIFF`]() create separate cache entries.
-   For multi-key read commands such as [`MGET`](),
    the ordering of the keys is significant. For example `MGET name:1 name:2` is
    cached separately from `MGET name:2 name:1` because the server returns the
    values in the order you specify.
-   Boolean or numeric values calculated from data types (for example
    [`SISMEMBER`]()) and
    [`LLEN`]() are cached separately from the
    object they refer to.

Usage recommendations

Like any caching system, client-side caching has some limitations:

-   The cache has only a limited amount of memory available. When the limit
    is reached, the client must *evict* potentially useful items from the
    cache to make room for new ones.
-   Cache misses, tracking, and invalidation messages always add a slight
    performance penalty.

Below are some guidelines to help you use client-side caching efficiently, within these
limitations:

-   **Use a separate connection for data that is not cache-friendly**:
    Caching gives the most benefit
    for keys that are read frequently and updated infrequently. However, you
    may also have data, such as counters and scoreboards, that receives frequent
    updates. In cases like this, the performance overhead of the invalidation
    messages can be greater than the savings made by caching. Avoid this problem
    by using a separate connection *without* client-side caching for any data that is
    not cache-friendly.
-   **Estimate how many items you can cache**: The client libraries let you
    specify the maximum number of items you want to hold in the cache. You
    can calculate an estimate for this number by dividing the
    maximum desired size of the
    cache in memory by the average size of the items you want to store
    (use the [`MEMORY USAGE`]()
    command to get the memory footprint of a key). For example, if you had
    10MB (or 10485760 bytes) available for the cache, and the average
    size of an item was 80 bytes, you could fit approximately
    10485760 / 80 = 131072 items in the cache. Monitor memory usage
    on your server with a realistic test load to adjust your estimate
    up or down.

    ## Reference

    The Redis server implements extra features for client-side caching that are not used by
    the main Redis clients, but may be useful for custom clients and other
    advanced applications. See
    [Client-side caching reference]()
    for a full technical guide to all the options available for client-side caching.


Conditional execution

Summary: Understand how `NRedisStack` uses conditional execution

`When` conditions

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

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

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

[code example]

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

Transaction conditions

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

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

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

[code example]

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

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

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

var watchedTrans = new Transaction(db);

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

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

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