Batch operations
A batch runs several operations of mixed types, puts, gets, and deletes together, in one round trip. This article shows you how to build a KvBatchRequest, execute it, and read the per-operation results, including the values returned by the gets.
Use a batch when you have related operations of different kinds to send together. Bulk operations move many keys of one operation type; a batch lets you combine a put, a get, and a delete in a single call.
This article uses types and helpers from two namespaces beyond your client bootstrap. Add both:
using Clustron.Zaris.Abstractions; // KvBatchRequest, KvBatchItem, KvBatchOp
using Clustron.Zaris.Client.Helpers; // the GetValue<T>() extension method
Build and execute a batch
Construct a KvBatchRequest with a list of KvBatchItem. Give each item an Index, an Op (KvBatchOp.Put, KvBatchOp.Get, or KvBatchOp.Delete), and a Key. Put operations also set PutValue. Then pass the request to ExecuteBatchAsync<T>.
var batch = new KvBatchRequest
{
Items = new List<KvBatchItem>
{
new KvBatchItem { Index = 0, Op = KvBatchOp.Put, Key = "user:1", PutValue = "Ali" },
new KvBatchItem { Index = 1, Op = KvBatchOp.Put, Key = "user:2", PutValue = "Sarah" },
new KvBatchItem { Index = 2, Op = KvBatchOp.Delete, Key = "user:3" },
new KvBatchItem { Index = 3, Op = KvBatchOp.Get, Key = "user:1" }
}
};
var response = await client.ExecuteBatchAsync<string>(batch);
The signature is:
Task<KvBatchResponse> ExecuteBatchAsync<T>(
KvBatchRequest request,
CancellationToken ct = default);
The type parameter T is the type that get results decode to. In the example above, T is string because the get reads a string. If your batch reads values of different types, see Read values from a mixed-type batch.
Read the results
ExecuteBatchAsync returns a KvBatchResponse whose Results list holds one KvBatchItemResult per operation. Each result carries the Index you assigned, so match results back to the operations you sent by Index rather than by position.
foreach (var result in response.Results)
{
if (result.Op == KvBatchOp.Get && result.Success)
{
string? value = result.GetValue<string>();
Console.WriteLine($"[{result.Index}] {value}");
}
else if (!result.Success)
{
Console.WriteLine($"[{result.Index}] {result.Op} failed: {result.Status}");
}
}
Each KvBatchItemResult exposes:
| Member | Type | Description |
|---|---|---|
Index | int | The index you assigned to the source item. Use it to correlate results with requests. |
Op | KvBatchOp | The operation this result came from. |
Success | bool | Whether this operation succeeded. |
Status | KvStatus | The specific outcome, for example NotFound for a get on a missing key. |
Error | string? | A message when the operation failed. |
To read the value from a get result, call the GetValue<T>() extension method, which deserializes the returned bytes into the type you ask for.
Read values from a mixed-type batch
When a batch reads values of different types, pick a general type argument for ExecuteBatchAsync (such as object) and decode each get result with GetValue<T>(), naming the concrete type per item.
var response = await client.ExecuteBatchAsync<object>(batch);
var customer = response.Results
.First(r => r.Index == 3)
.GetValue<Customer>();
GetValue<T>() deserializes the result's bytes and works the same whether the store is in-process or remote. Prefer it over reading ValueObject directly, because ValueObject is populated as an in-process convenience and is decoded as the batch's single type argument T.
Execution semantics
The client sends the items together and the store executes them as a group, one round trip per owning node. Two properties matter when you rely on a batch:
- Operations execute in order, and results preserve that order through the
Indexfield. - A batch is not transactional. Some operations can succeed while others fail, so you must check
Successon each result.
A batch groups operations for efficiency; it does not make them atomic. If a batch reads a value and then writes based on it, another writer can change the key in between. When you need all-or-nothing semantics, use a transaction instead.
Batch versus bulk
Both send multiple operations in one call. The difference is what they group.
| Aspect | Bulk operations | Batch operations |
|---|---|---|
| Operation types | One type per call (PutManyAsync, GetManyAsync, DeleteManyAsync) | Mixed (put, get, delete together) |
| Value types | One type T per call | Can differ per item; decode with GetValue<T>() |
| Result shape | IReadOnlyList<KvResult> | KvBatchResponse.Results |
| Best for | Moving many keys of the same shape | Grouping related operations of different kinds |
| Transactional | No | No |