StackExchange.Redis

Redis Vector Sets

Redis Vector Sets provide efficient storage and similarity search for vector data. SE.Redis provides a strongly-typed API for working with vector sets.

Prerequisites

Redis Version

Vector Sets require Redis 8.0 or later.

Quick Start

Note that the vectors used in these examples are small for illustrative purposes. In practice, you would commonly use much larger vectors. The API is designed to efficiently handle large vectors - in particular, the use of ReadOnlyMemory<T> rather than arrays allows you to work with vectors in “pooled” memory buffers (such as ArrayPool<T>), which can be more efficient than creating arrays - or even working with raw memory for example memory-mapped-files.

Adding Vectors

Add vectors to a vector set using VectorSetAddAsync:

var db = conn.GetDatabase();
var key = "product-embeddings";

// Create a vector (e.g., from an ML model)
var vector = new[] { 0.1f, 0.2f, 0.3f, 0.4f };

// Add a member with its vector
var request = VectorSetAddRequest.Member("product-123", vector.AsMemory());
bool added = await db.VectorSetAddAsync(key, request);

Adding Vectors with Attributes

You can attach JSON metadata to vectors for filtering:

var vector = new[] { 0.1f, 0.2f, 0.3f, 0.4f };
var request = VectorSetAddRequest.Member(
    "product-123",
    vector.AsMemory(),
    attributesJson: """{"category":"electronics","price":299.99}"""
);
await db.VectorSetAddAsync(key, request);

Find similar vectors using VectorSetSimilaritySearchAsync:

// Search by an existing member
var query = VectorSetSimilaritySearchRequest.ByMember("product-123");
query.Count = 10;
query.WithScores = true;

using var results = await db.VectorSetSimilaritySearchAsync(key, query);
if (results is not null)
{
    foreach (var result in results.Value.Results)
    {
        Console.WriteLine($"Member: {result.Member}, Score: {result.Score}");
    }
}

Or search by a vector directly:

var queryVector = new[] { 0.15f, 0.25f, 0.35f, 0.45f };
var query = VectorSetSimilaritySearchRequest.ByVector(queryVector.AsMemory());
query.Count = 10;
query.WithScores = true;

using var results = await db.VectorSetSimilaritySearchAsync(key, query);

Use JSON path expressions to filter results:

var query = VectorSetSimilaritySearchRequest.ByVector(queryVector.AsMemory());
query.Count = 10;
query.FilterExpression = "$.category == 'electronics' && $.price < 500";
query.WithAttributes = true; // Include attributes in results

using var results = await db.VectorSetSimilaritySearchAsync(key, query);

See Redis filtered search documentation for filter syntax.

Vector Set Operations

Getting Vector Set Information

var info = await db.VectorSetInfoAsync(key);
if (info is not null)
{
    Console.WriteLine($"Dimension: {info.Value.Dimension}");
    Console.WriteLine($"Length: {info.Value.Length}");
    Console.WriteLine($"Quantization: {info.Value.Quantization}");
}

Checking Membership

bool exists = await db.VectorSetContainsAsync(key, "product-123");

Removing Members

bool removed = await db.VectorSetRemoveAsync(key, "product-123");

Getting Random Members

// Get a single random member
var member = await db.VectorSetRandomMemberAsync(key);

// Get multiple random members
var members = await db.VectorSetRandomMembersAsync(key, count: 5);

Range Queries

Getting Members by Lexicographical Range

Retrieve members in lexicographical order:

// Get all members
using var allMembers = await db.VectorSetRangeAsync(key);
// ... access allMembers.Span, etc

// Get members in a specific range
using var rangeMembers = await db.VectorSetRangeAsync(
    key,
    start: "product-100",
    end: "product-200",
    count: 50
);
// ... access rangeMembers.Span, etc

// Exclude boundaries
using var members = await db.VectorSetRangeAsync(
    key,
    start: "product-100",
    end: "product-200",
    exclude: Exclude.Both
);
// ... access members.Span, etc

Enumerating Large Result Sets

For large vector sets, use enumeration to process results in batches:

await foreach (var member in db.VectorSetRangeEnumerateAsync(key, count: 100))
{
    Console.WriteLine($"Processing: {member}");
}

The enumeration of results is done in batches, so that the client does not need to buffer the entire result set in memory; if you exit the loop early, the client and server will stop processing and sending results. This also supports async cancellation:

using var cts = new CancellationTokenSource(); // cancellation not shown

await foreach (var member in db.VectorSetRangeEnumerateAsync(key, count: 100)
    .WithCancellation(cts.Token))
{
    // ...
}

Advanced Configuration

Quantization

Control vector compression:

var request = VectorSetAddRequest.Member("product-123", vector.AsMemory());
request.Quantization = VectorSetQuantization.Int8;  // Default
// or VectorSetQuantization.None
// or VectorSetQuantization.Binary
await db.VectorSetAddAsync(key, request);

Dimension Reduction

Use projection to reduce vector dimensions:

var request = VectorSetAddRequest.Member("product-123", vector.AsMemory());
request.ReducedDimensions = 128; // Reduce from original dimension
await db.VectorSetAddAsync(key, request);

HNSW Parameters

Fine-tune the HNSW index:

var request = VectorSetAddRequest.Member("product-123", vector.AsMemory());
request.MaxConnections = 32;           // M parameter (default: 16)
request.BuildExplorationFactor = 400;  // EF parameter (default: 200)
await db.VectorSetAddAsync(key, request);

Search Parameters

Control search behavior:

var query = VectorSetSimilaritySearchRequest.ByVector(queryVector.AsMemory());
query.SearchExplorationFactor = 500;  // Higher = more accurate, slower
query.Epsilon = 0.1;                  // Only return similarity >= 0.9
query.UseExactSearch = true;          // Use linear scan instead of HNSW
await db.VectorSetSimilaritySearchAsync(key, query);

Working with Vector Data

Retrieving Vectors

Get the approximate vector for a member:

using var vectorLease = await db.VectorSetGetApproximateVectorAsync(key, "product-123");
if (vectorLease != null)
{
    ReadOnlySpan<float> vector = vectorLease.Value.Span;
    // Use the vector data
}

Managing Attributes

Get and set JSON attributes:

// Get attributes
var json = await db.VectorSetGetAttributesJsonAsync(key, "product-123");

// Set attributes
await db.VectorSetSetAttributesJsonAsync(
    key,
    "product-123",
    """{"category":"electronics","updated":"2024-01-15"}"""
);

Inspect HNSW graph connections:

// Get linked members
using var links = await db.VectorSetGetLinksAsync(key, "product-123");
if (links != null)
{
    foreach (var link in links.Value.Span)
    {
        Console.WriteLine($"Linked to: {link}");
    }
}

// Get links with similarity scores
using var linksWithScores = await db.VectorSetGetLinksWithScoresAsync(key, "product-123");
if (linksWithScores != null)
{
    foreach (var link in linksWithScores.Value.Span)
    {
        Console.WriteLine($"Linked to: {link.Member}, Score: {link.Score}");
    }
}

Memory Management

Vector operations return Lease<T> for efficient memory pooling. Always dispose leases:

// Using statement (recommended)
using var results = await db.VectorSetSimilaritySearchAsync(key, query);

// Or explicit disposal
var results = await db.VectorSetSimilaritySearchAsync(key, query);
try
{
    // Use results
}
finally
{
    results?.Dispose();
}

Performance Considerations

Batch Operations

For bulk inserts, consider using pipelining:

var batch = db.CreateBatch();
var tasks = new List<Task<bool>>();

foreach (var (member, vector) in vectorData)
{
    var request = VectorSetAddRequest.Member(member, vector.AsMemory());
    tasks.Add(batch.VectorSetAddAsync(key, request));
}

batch.Execute();
await Task.WhenAll(tasks);

Search Optimization

Range Query Pagination

Prefer enumeration for large result sets to avoid loading everything into memory:

// Good: loads results in batches, processes items individually
await foreach (var member in db.VectorSetRangeEnumerateAsync(key))
{
    await ProcessMemberAsync(member);
}

// Avoid: loads all results at once
using var allMembers1 = await db.VectorSetRangeAsync(key);

// Avoid: loads results in batches, but still loads everything into memory at once
var allMembers2 = await db.VectorSetRangeEnumerateAsync(key).ToArrayAsync();

Common Patterns

// 1. Store document embeddings
var embedding = await GetEmbeddingFromMLModel(document);
var request = VectorSetAddRequest.Member(
    documentId,
    embedding.AsMemory(),
    attributesJson: $$"""{"title":"","date":""}"""
);
await db.VectorSetAddAsync("documents", request);

// 2. Search for similar documents
var queryEmbedding = await GetEmbeddingFromMLModel(searchQuery);
var query = VectorSetSimilaritySearchRequest.ByVector(queryEmbedding.AsMemory());
query.Count = 10;
query.WithScores = true;
query.WithAttributes = true;

using var results = await db.VectorSetSimilaritySearchAsync("documents", query);

Recommendation System

// Find similar items based on an item the user liked
var query = VectorSetSimilaritySearchRequest.ByMember(userLikedItemId);
query.Count = 20;
query.FilterExpression = "$.inStock == true && $.price < 100";
query.WithScores = true;

using var recommendations = await db.VectorSetSimilaritySearchAsync("products", query);

See Also