> For the complete documentation index, see [llms.txt](https://docs.rumi.systems/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rumi.systems/rumi-core/concepts/transactions.md).

# Transactions

## Overview

As described in [Message Processing](/rumi-core/concepts/microservice-operation/message-processing.md) and [Cluster Consensus](/rumi-core/concepts/microservice-operation/cluster-consensus.md), the AEP engine uses transactions to ensure state consensus in a service cluster. While a microservice processes messages, the AEP engine prepares and atomically replicates transactions in a manner that ensures the primary instance's store is identically consistent with the store on each of the backup instances in the cluster.

## Transaction Elements

What constitutes a transaction is determined by the service consensus model. The followng lists the contents of a transaction:

* **State Replicated Microservices**
  * Inbound message metadata
  * Service store change log
  * Outbound messages sent outbound as part of the inbound message processing
* **Event Sourced Microservices**
  * Inbound message metadata
  * Inbound messages

By default, each inbound message starts and terminates a transaction. However, the AEP engine can bundles multiple inbound messages into a single transaction and commit the entire batch as a single atomic unit. This is called *adaptive batching.*

## The Transaction Pipeline

When an inbound message enters the engine's event dispatch loop, the message is attached to the current transaction. As the engine routes the message through its handlers, it incrementally updates the [elements](#transaction-elements) of the transaction. On return from the message handler, the engine decides whether to commit the transaction or not. If it does, it

* Marks the transaction as complete
* Creates a new current transaction
* Dispatches the completed transaction for commitment i.e. replication and persistence across the cluster to achieve cluster consensus.

Transaction commitment occurs in a **pipelined** manner: while a transaction is being replicated across the cluster, the engine continues to process and commit new inbound messages under subsequent transactions. A transaction in the process of being committed is called an **inflight** transaction and the set of all such transactions is called the **transaction pipeline**.

## Transaction Commit Legs

Committing a transaction is performed in three stages, referred to as **legs**:

| Leg       | What it does                                                                                 |
| --------- | -------------------------------------------------------------------------------------------- |
| **Leg 1** | Store commit submission - hands the transaction to the store for replication and persistence |
| **Leg 2** | Send commit submission - submits the transaction's outbound messages to their bus bindings   |
| **Leg 3** | Commit completion - completes the transaction once every send commit has been acknowledged   |

Leg 1 and leg 3 are always executed by the engine thread. Leg 2 is the exception: when the runtime is tuned for latency, the engine executes it on the store thread the moment the store commit completes, rather than dispatching it back through the engine's multiplexer. Skipping that dispatch removes a measurable amount of latency from the commit path, which is why it is the default.

This means that under a latency optimized engine the commit is executed by two threads rather than one. The engine accounts for this internally: leg 3 will not complete a transaction while leg 2 is still working on it. In practice leg 2 finishes long before the send commits it submitted are acknowledged, so leg 3 does not wait.

The behaviour is controlled by two settings, both described in the [Configuration Reference](/rumi-core/reference/configuration.md):

* **`leg2InStoreThread`** (default `true`) - set to `false` to execute leg 2 on the engine thread as well, making the commit single threaded. This has no effect unless the runtime is tuned for latency, and it costs latency, so it is intended as a diagnostic rather than a tuning option.
* **`leg2CompletionTimeout`** (default 10 seconds, in microseconds) - an upper bound on how long leg 3 will wait for leg 2. It exists so that an engine can never be held up indefinitely; if it expires the engine logs a severe message and completes the transaction anyway.

{% hint style="info" %}
Leg 2's duration is reported by the engine's transaction statistics, so the cost of this stage is visible without any special configuration.
{% endhint %}

## Adaptive Batching

By default, an AEP engine processes each inbound message in a single transaction. It is possible to configure the engine to batch up the processing of several inbound messages into a single transaction. This feature us called **adaptive batching**.

Adaptive batching can significantly improve throughput. However, this is generally at the cost of increased latency of outbound messages since the outbound messages for the first message processed in a transaction won't be sent until the last message in the transaction has been processed and the transaction dispatched for commit.

The batching behavior is adaptive in nature because the engine commits a transaction automatically when either a configured adaptive batch ceiling is reached or the engine detects there are no more messages immediately available to process. In other words, if there are messages arriving significantly fast that they can be added to the current transaction with no additional weight, then the batch size of the transaction will grow to the configured batch ceiling. However, if there is a slight lull in the inbound traffic pattern that would cause the engine to have to wait for the next message to fill the batch, then the engine does not wait and will immediately close the batch.
