docs/Arrays.md
Redis Arrays provide sparse arrays of arbitrary Redis values with unsigned array indexes and a notional write head. SE.Redis exposes the array API as experimental Redis 8.8 APIs; callers should expect details to change while the server feature is still in preview.
Arrays require Redis 8.8 or later. The APIs are marked with the SER006 experimental warning.
Use ArraySetAsync and ArrayGetAsync to write and read individual cells:
var db = conn.GetDatabase();
RedisKey key = "events";
bool inserted = await db.ArraySetAsync(key, 0, "created");
RedisValue value = await db.ArrayGetAsync(key, 0);
RedisValue missing = await db.ArrayGetAsync(key, 1);
Console.WriteLine(inserted); // True when the cell did not previously have a value
Console.WriteLine(value); // created
Console.WriteLine(missing.IsNull); // True
Array indexes use RedisArrayIndex, with implicit conversions from int, long, and ulong. This allows normal small indexes to be used directly, while still allowing the full unsigned index range when needed.
await db.ArraySetAsync(key, 42, "answer");
await db.ArraySetAsync(key, new RedisArrayIndex(10_000_000UL), "large index");
Arrays are sparse: unset cells do not have values. ArrayLengthAsync reports the notional length, which is the highest used index plus one. ArrayCountAsync reports only cells that currently have values.
await db.KeyDeleteAsync(key);
await db.ArraySetAsync(key, 0, "a");
await db.ArraySetAsync(key, 10, "b");
RedisArrayIndex length = await db.ArrayLengthAsync(key); // 11
RedisArrayIndex count = await db.ArrayCountAsync(key); // 2
To write a contiguous range, pass the first index and the values:
int inserted = await db.ArraySetAsync(key, 0, ["a", "b", "c"]);
To write multiple specific indexes, use RedisArrayEntry values:
await db.ArraySetAsync(key,
[
new RedisArrayEntry(0, "alpha"),
new RedisArrayEntry(5, "bravo"),
new RedisArrayEntry(100, "charlie"),
]);
The returned int is the number of cells that were newly filled.
Read selected indexes with ArrayGetAsync:
RedisValue[] values = await db.ArrayGetAsync(key, [0, 5, 6, 100]);
Read a range with ArrayGetRangeAsync. Ranges can be read forward or backward:
RedisValue[] forward = await db.ArrayGetRangeAsync(key, 0, 5);
RedisValue[] reverse = await db.ArrayGetRangeAsync(key, 5, 0);
For sparse arrays, use ArrayScanAsync to return only populated cells in a range:
RedisArrayEntry[] entries = await db.ArrayScanAsync(key, 0, 100, limit: 50);
foreach (var entry in entries)
{
Console.WriteLine($"{entry.Index}: {entry.Value}");
}
Delete a single cell with ArrayDeleteAsync:
bool removed = await db.ArrayDeleteAsync(key, 5);
Delete multiple specific cells by index:
int removedCount = await db.ArrayDeleteAsync(key, [0, 5, 100]);
Delete one or more ranges:
await db.ArrayDeleteRangeAsync(key, 10, 20);
await db.ArrayDeleteRangeAsync(key,
[
new RedisArrayRange(100, 199),
new RedisArrayRange(500, 599),
]);
Use ArrayGrepRequest with ArrayGrepAsync to search values. When Start or End is not specified, the server's open-ended lower or upper bound is used.
var request = new ArrayGrepRequest
{
Limit = 10,
};
request.AddPredicate(ArrayGrepRequest.Predicate.Match("error"));
RedisArrayEntry[] matches = await db.ArrayGrepAsync(key, request);
foreach (var match in matches)
{
Console.WriteLine(match.Index);
}
Set IncludeValues to return values along with the matching indexes:
var request = new ArrayGrepRequest
{
IncludeValues = true,
};
request.AddPredicate(ArrayGrepRequest.Predicate.Regex("^ERR[0-9]+"));
RedisArrayEntry[] matches = await db.ArrayGrepAsync(key, request);
foreach (var match in matches)
{
Console.WriteLine($"{match.Index}: {match.Value}");
}
Multiple predicates can be combined. By default, predicates are combined as OR; set IsIntersection to combine them as AND.
var request = new ArrayGrepRequest
{
IsIntersection = true,
};
request.AddPredicate(ArrayGrepRequest.Predicate.Match("redis"));
request.AddPredicate(ArrayGrepRequest.Predicate.Glob("*array*"));
RedisArrayEntry[] matches = await db.ArrayGrepAsync(key, request);
Arrays have a write head used by insert operations. ArrayInsertAsync writes at the current write head and advances it.
RedisArrayIndex first = await db.ArrayInsertAsync(key, "first");
RedisArrayIndex second = await db.ArrayInsertAsync(key, "second");
RedisArrayIndex? next = await db.ArrayNextAsync(key);
Move the write head with ArraySeekAsync:
bool moved = await db.ArraySeekAsync(key, 1_000);
RedisArrayIndex written = await db.ArrayInsertAsync(key, "later");
Use ArrayRingAsync to keep at most a fixed number of cells and wrap writes around that capacity:
for (int i = 0; i < 10; i++)
{
await db.ArrayRingAsync(key, maxLength: 5, value: i);
}
RedisArrayIndex count = await db.ArrayCountAsync(key); // 5
ArrayLastItemsAsync is intended for this capped ring-buffer model. It reads the last values in the ring-buffer sense, where "last" relates to the retained values after wrap-around and trimming:
RedisValue[] last = await db.ArrayLastItemsAsync(key, count: 10);
RedisValue[] lastReversed = await db.ArrayLastItemsAsync(key, count: 10, reverse: true);
Use ArrayOperationAsync for simple server-side operations over a range:
RedisValue sum = await db.ArrayOperationAsync(key, 0, 10, ArrayOperation.Sum);
RedisValue used = await db.ArrayOperationAsync(key, 0, 10, ArrayOperation.Used);
RedisValue matches = await db.ArrayOperationAsync(key, 0, 10, ArrayOperation.Match, "error");
Use ArrayInfoAsync for metadata:
ArrayInfo info = await db.ArrayInfoAsync(key);
Console.WriteLine($"Count: {info.Count}");
Console.WriteLine($"Length: {info.Length}");
Console.WriteLine($"Next insert index: {info.NextInsertIndex}");