Batch: Transaction and Pipelining (Glide 2.0)
In Valkey GLIDE 2.0, the concept of Batch and ClusterBatch replaces the previous Transaction and ClusterTransaction APIs. This change provides greater flexibility by supporting both atomic batches (Transactions) and non-atomic batches (Pipelining), while ensuring easy configuration and clear, detailed examples for each scenario.
Introduction
Section titled “Introduction”Glide 2.0 introduces a robust Batch API with two primary modes:
- Atomic Batch: Guarantees that all commands in a batch execute as a single, atomic unit. No other commands can interleave (similar to MULTI/EXEC).
- Non-Atomic Batch (Pipeline): Sends multiple commands in one request without atomic guarantees. Commands can span multiple slots/nodes in a cluster and do not block other operations from being processed between them.
Both modes leverage the same classes— Batch for standalone mode and ClusterBatch for cluster mode — distinguished by an isAtomic flag. Extra configuration is provided via BatchOptions or ClusterBatchOptions, allowing control over timeouts, routings, and retry strategies.
Key Concepts
Section titled “Key Concepts”Atomic Batch (Transaction)
Section titled “Atomic Batch (Transaction)”- Definition: A set of commands executed together as a single, indivisible operation.
- Guarantees: Sequential execution without interruption. Other clients cannot interleave commands between the batched operations.
- Slot Constraint (Cluster Mode): When running against a cluster, all keys in an atomic batch must map to the same hash slot. Mixing keys from different slots will cause the transaction to fail.
- Underlying Valkey: Equivalent to
MULTI/EXECValkey commands. - Use Case: When you need consistency and isolation.
- See: Valkey Transactions.
Non-Atomic Batch (Pipeline)
Section titled “Non-Atomic Batch (Pipeline)”- Definition: A group of commands sent in a single request, but executed without atomicity or isolation.
- Behavior: Commands may be processed on different slots/nodes (in cluster mode), and other operations from different clients may interleave during execution.
- Underlying Valkey: Similar to pipelining, minimizing round-trip latencies by sending all commands at once.
- Use Case: Bulk reads or writes where each command is independent.
- See: Valkey Pipelines.
Classes and API
Section titled “Classes and API”For standalone (non-cluster, cluster mode disabled) clients.
from glide import Batch
# Create an atomic batch (transaction)batch = Batch(True)# Create a non-atomic batch (pipeline)batch = Batch(False)ClusterBatch
Section titled “ClusterBatch”For cluster (cluster mode enabled) clients (Mirrors Batch but routes commands based on slot ownership,
splitting into sub-pipelines if needed, Read more in Multi-Node support).
from glide import ClusterBatch
# Create an atomic cluster batch (must use keys mapping to same slot)batch = ClusterBatch(True)# Create a non-atomic cluster batch (pipeline may span multiple slots)const batch = ClusterBatch(False)Error handling - Raise on Error
Section titled “Error handling - Raise on Error”Determines how errors are surfaced when calling exec(...). It is passed directly:
# Standalone Modeasync def exec( self, batch: Batch, raise_on_error: bool, options: Optional[BatchOptions] = None,)
# Cluster Modeasync def exec( self, batch: ClusterBatch, raise_on_error: bool, options: Optional[ClusterBatchOptions] = None,)Behavior:
-
raiseOnError = true: When set totrue, the first encountered error within the batch (after all configured retries and redirections have been executed) is raised as aRequestException. -
raiseOnError = false:- When set to
false, errors are returned as part of the response array rather than thrown. - Each failed command’s error details appear as a
RequestExceptioninstance in the corresponding position of the returned array. - Allows processing of both successful and failed commands together.
- When set to
Example:
# Cluster pipeline with raiseOnError = Falsebatch = ClusterBatch(False)
batch.set(key, "hello")batch.lpop(key)batch.delete([key])batch.rename(key, key2)
result = await GlideClusterClient.exec(batch, raise_on_error=False)print("Result is:", result)# Output: Result is: ['OK', RequestError('WRONGTYPE: Operation against a key holding the wrong kind of value'), 1, RequestError('An error was signalled by the server: - ResponseError: no such key')]# Transaction with raiseOnError = truebatch = ClusterBatch(True)
batch.set(key, "hello")batch.lpop(key)batch.delete([key])batch.rename(key, key2)
try: await GlideClient.exec(batch, raise_on_error=False)except RequestsError as e: print("Batch execution aborted:", e)# Output: Batch execution aborted: WRONGTYPE: Operation against a key holding the wrong kind of valueBatchOptions
Section titled “BatchOptions”Configuration for standalone batches.
| Option | Type | Default | Description |
|---|---|---|---|
timeout | Integer | Client-level request timeout (e.g., 5000 ms) | Maximum time in milliseconds to wait for the batch response. If exceeded, a timeout error is returned for the batch. |
from glide import BatchOptions
batch_options = BatchOptions(timeout=2000) # 2 secondsClusterBatchOptions
Section titled “ClusterBatchOptions”Configuration for cluster batches.
| Option | Type | Default | Description |
|---|---|---|---|
timeout | Integer | Client’s requestTimeout | Maximum time in milliseconds to wait for entire cluster batch response. |
retryStrategy | ClusterBatchRetryStrategy | null (defaults to no retries) | Configures retry settings for server and connection errors. Not supported if isAtomic = true — retry strategies only apply to non-atomic (pipeline) batches. |
route | SingleNodeRoute | null | Configures single-node routing for the batch request. |
ClusterBatchRetryStrategy
Defines retry behavior (only for non-atomic cluster batches).
| Option | Type | Default | Description |
|---|---|---|---|
retryServerError | boolean | false | Retry commands that fail with retriable server errors (e.g.TRYAGAIN). May cause out-of-order results. |
retryConnectionError | boolean | false | Retry entire batch on connection failures. May cause duplicate executions since server might have processed the request before failure. |
from glide import BatchRetryStrategy
retry_strategy = BatchRetryStrategy(retry_server_error=True, retry_connection_error= False)Full usage
Section titled “Full usage”from glide import ClusterBatchOptions, BatchRetryStrategy
retry_strategy = BatchRetryStrategy(retry_server_error=True, retry_connection_error=False)options = ClusterBatchOptions(retry_strategy=retry_strategy)Configuration Details
Section titled “Configuration Details”Timeout
- Specifies the maximum time (in milliseconds) to wait for the batch (atomic or non-atomic) request to complete.
- If the timeout is reached before receiving all responses, the batch fails with a timeout error.
- Defaults to the client’s
requestTimeoutif not explicitly set.
Retry Strategies (Cluster Only, Non-Atomic Batches)
-
Retry on Server Errors
- Applies when a command fails with a retriable server error (e.g.,
TRYAGAIN). - Glide will automatically retry the failed command on the same node or the new master, depending on the topology update.
- ⚠️ Caveat: Retried commands may arrive later than subsequent commands, leading to out-of-order execution if commands target the same slot.
- Applies when a command fails with a retriable server error (e.g.,
-
Retry on Connection Errors
- If a connection error occurs, the entire batch (or sub-pipeline, Read more in Multi-Node support) is retried from the start.
- ⚠️ Caveat: If the server received and processed some or all commands before the connection failure, retrying the batch may lead to duplicate executions.
Route (Cluster Only)
Configures single-node routing for the batch request. The client will send the batch to the specified node defined by route.
If a redirection error occurs:
- For Atomic Batches (Transactions): The entire transaction will be redirected.
- For Non-Atomic Batches (Pipelines): only the commands that encountered redirection errors will be redirected.
Usage Examples
Section titled “Usage Examples”Standalone (Atomic Batch)
Section titled “Standalone (Atomic Batch)”from glide import ( GlideClientConfiguration, NodeAddress, GlideClient, BatchOptions,)
# Create client configurationaddresses = [ NodeAddress("server_primary.example.com", 6379), NodeAddress("server_replica.example.com", 6379)]config = GlideClientConfiguration(addresses)
# Initialize clientclient = await GlideClient.create(config)
# Configure batch optionsoptions = BatchOptions(timeout=2000)
# Create atomic batch (true indicates atomic/transaction mode)atomic_batch = Batch(True)atomic_batch.set("account:source", "100")atomic_batch.set("account:dest", "0")atomic_batch.incrby("account:dest", 50)atomic_batch.decrby("account:source", 50)atomic_batch.get("account:source")
try: # Execute with raiseOnError = true results = await client.exec(atomic_batch, raise_on_error=True, options=options) print("Atomic Batch Results:", results) # Expected output: Atomic Batch Results: ['OK', 'OK', 50, 50, '50'] except RequestError as e: print(f"Batch failed:", e)Standalone (Non-Atomic Batch)
from glide import ( GlideClientConfiguration, NodeAddress, GlideClient, Batch, BatchOptions)
# Create client configurationaddresses = [ NodeAddress("localhost", 6379)]config = GlideClientConfiguration(addresses)
# Initialize clientclient = await GlideClient.create(config)
# Configure batch optionsoptions = BatchOptions(timeout=2000) # 2-second timeout
# Create non-atomic batch (False indicates pipeline mode)pipeline = Batch(False)pipeline.set("temp:key1", "value1")pipeline.set("temp:key2", "value2")pipeline.get("temp:key1")pipeline.get("temp:key2")
# Execute with raise_on_error = Falseresults = await client.exec(pipeline, raise_on_error=False, options=options)print("Pipeline Results:", results)# Expected output: Pipeline Results: ['OK', 'OK', 'value1', 'value2']Cluster (Atomic Batch)
Section titled “Cluster (Atomic Batch)”from glide import ( GlideClusterClientConfiguration, NodeAddress, GlideClusterClient, ClusterBatch, ClusterBatchOptions)
# Initialize cluster client configurationaddresses = [ NodeAddress("127.0.0.1", 6379)]config = GlideClusterClientConfiguration(addresses)
# Initialize clientglideClusterClient = await GlideClusterClient.create(config)
# Configure atomic batch optionsoptions = ClusterBatchOptions(timeout=3000) # 3-second timeout
# Create atomic cluster batch (all keys map to same slot)atomicClusterBatch = ClusterBatch(True)atomicClusterBatch.set("user:100:visits", "1")atomicClusterBatch.incrby("user:100:visits", 5)atomicClusterBatch.get("user:100:visits")
try: # Execute with raise_on_error = True clusterResults = await glideClusterClient.exec(atomicClusterBatch, raise_on_error=True, options=options) print("Atomic Cluster Batch:", clusterResults) # Expected output: Atomic Cluster Batch: ['OK', 6, '6']except RequestError as e: print("Atomic cluster batch failed:", e)Cluster (Non-Atomic Batch / Pipeline)
Section titled “Cluster (Non-Atomic Batch / Pipeline)”from glide import ( GlideClusterClientConfiguration, NodeAddress, GlideClusterClient, ClusterBatch, ClusterBatchOptions, ClusterBatchRetryStrategy)
# Initialize cluster client configurationaddresses = [ NodeAddress("localhost", 6379)]config = GlideClusterClientConfiguration(addresses)
# Initialize clientglideClusterClient = await GlideClusterClient.create(config)
# Configure retry strategy and pipeline optionsretry_strategy = ClusterBatchRetryStrategy( retry_server_error=False, retry_connection_error=True)
pipeline_options = ClusterBatchOptions( timeout=5000, # 5-second timeout retry_strategy=retry_strategy)
# Create pipeline spanning multiple slotspipeline_cluster = ClusterBatch(False) # False indicates non-atomic (pipeline)pipeline_cluster.set("page:home:views", "100")pipeline_cluster.incrby("page:home:views", 25)pipeline_cluster.get("page:home:views")pipeline_cluster.lpush("recent:logins", ["user1"])pipeline_cluster.lpush("recent:logins", ["user2"])pipeline_cluster.lrange("recent:logins", 0, 1)
# Execute with raise_on_error = Falsepipeline_results = await glideClusterClient.exec(pipeline_cluster, raise_on_error=False, options=pipeline_options)print("Pipeline Cluster Results:", pipeline_results)# Expected output: Pipeline Cluster Results: ['OK', 125, '125', 1, 2, ['user2', 'user1']]Multi-Node Support
Section titled “Multi-Node Support”While atomic batches (transactions) are restricted to a single Valkey node— all commands must map to the same hash slot in cluster mode—non-atomic batches (pipelines) can span multiple nodes. This enables operations that involve keys located in different slots or even multi-node commands.
When Glide processes a pipeline:
- Slot Calculation and Routing: For each key-based command (e.g.,
GET,SET), Glide computes the hash slot and determines which node owns that slot. If a command does not reference a key (e.g.,INFO), it follows the command’s default request policy. - Grouping into Sub-Pipelines: Commands targeting the same node are grouped together into a sub-pipeline. Each sub-pipeline contains all commands destined for a specific node.
- Dispatching Sub-Pipelines: Glide sends each sub-pipeline independently to its target node as a pipelined request.
- Aggregating Responses: Once all sub-pipelines return their results, Glide reassembles the responses into a single array, preserving the original command order. Multi-node commands are automatically split and dispatched appropriately.
Retry Strategy in Pipelines
When errors occur during pipeline execution, Glide handles them efficiently and granularly — each command in the pipeline receives its own response, whether successful or not. This means pipeline execution is not all-or-nothing: some commands may succeed while others may return errors (See the ClusterBatchRetryStrategy configuration and error handling details in the classes and API section for how to handle these errors programmatically).
Glide distinguishes between different types of errors and handles them as follows:
- Redirection Errors (e.g.,
MOVEDorASK): These are always handled automatically. Glide will update the topology map if needed and redirect the command to the appropriate node, regardless of the retry configuration. - Retriable Server Errors (e.g.,
TRYAGAIN): If theretryServerErroroption is enabled in the batch’s retry strategy, Glide will retry commands that fail with retriable server errors.
⚠️ Retrying may cause out-of-order execution for commands targeting the same slot. - Connection Errors:
If the
retryConnectionErroroption is enabled, Glide will retry the batch if a connection failure occurs.
⚠️ Retrying after a connection error may result in duplicate executions, since the server might have already received and processed the request before the error occurred.
Retry strategies are currently supported only for non-atomic (pipeline) cluster batches. You can configure these using the ClusterBatchRetryStrategy options:
retryServerError:Retry on server errors.retryConnectionError:Retry on connection failures.
Example Scenario:
Suppose you issue the following commands:
MGET key {key}:1SET key "value"When keys are empty, the result is expected to be:
[null, null]OKHowever, suppose the slot of key is migrating. In this case, both commands will return an ASK error and be redirected.
Upon ASK redirection, a multi-key command (like MGET) may return a TRYAGAIN error (triggering a retry), while the SET command succeeds immediately.
This can result in an unintended reordering of commands if the first command is retried after the slot stabilizes:
["value", null]OKDeprecation Notice
Section titled “Deprecation Notice”-
Deprecated Classes:
TransactionandClusterTransactionare deprecated in Glide 2.0. -
Replacement: Use
BatchorClusterBatchwithisAtomic = trueto achieve transaction-like (atomic) behavior. -
Migration Tips:
- Replace calls to
new Transaction()withnew Batch(true). - Replace calls to
new ClusterTransaction()withnew ClusterBatch(true). - Replace
client.exec(transaction)withclient.exec(batch, raiseOnError)orclient.exec(batch, raiseOnError, options).
- Replace calls to