back to all blogs

Benchmarking Parallel vs Serial Transaction Scheduling in Distributed Ledgers

Distributed SystemsBlockchainPerformanceResearch

⚠️ WARNING: This is a sample blog to demonstrate available writing features. (Feature: Blockquote / Callout)

During my research fellowship at the Indian Institute of Technology, Hyderabad, we investigated throughput bottlenecks imposed by serial transaction validation in distributed ledger technologies (DLTs).

Most early blockchain platforms process transactions strictly sequentially to prevent race conditions and double-spending. However, this creates an artificial bottleneck on modern multi-core server hardware. By decoupling independent transactions, distributed ledgers can execute state mutations concurrently without violating ACID guarantees1.


1. The Core Bottleneck: Serial Execution

(Feature: Display Math & Inline LaTeX)

In high-frequency ledger state transitions, two transactions that touch disjoint address spaces in the global Merkle state tree have zero data dependency on each other:

TxA:Alice→transfer($50)BobTx_A: \text{Alice} \xrightarrow{\text{transfer}(\$50)} \text{Bob} TxB:Charlie→transfer($100)DaveTx_B: \text{Charlie} \xrightarrow{\text{transfer}(\$100)} \text{Dave}

Executing TxBTx_B only after TxATx_A finishes serializes independent memory writes and wastes CPU idle cycles. Under ideal concurrency with NN worker threads, Amdahl’s Law speedup is expressed as:

Speedup=TserialTparallel=∑i=1Ntimax⁡j∈[1,P]∑k∈Bjtk\text{Speedup} = \frac{T_{\text{serial}}}{T_{\text{parallel}}} = \frac{\sum_{i=1}^N t_i}{\max_{j \in [1, P]} \sum_{k \in \mathcal{B}_j} t_k}

Where PP represents available execution cores and Bj\mathcal{B}_j represents the batch of non-conflicting transactions assigned to core jj.


2. Architecture & Conflict Graph Scheduling

To achieve parallel execution without runtime lock contention, we developed a custom Transaction Processor on Hyperledger Sawtooth that executes across three coordinated pipeline stages:

  1. Explicit Address Declaration: Each transaction pre-declares all memory addresses it intends to read from (inputs) and write to (outputs).
  2. Conflict Graph Construction: The scheduler constructs a Directed Acyclic Graph (DAG) in O(V+E)\mathcal{O}(V + E) time, where nodes represent transactions and directed edges represent state hazards (Read-After-Write, Write-After-Read, Write-After-Write).
  3. Concurrent Worker Dispatch: Independent sub-graphs with topological in-degree 00 are dispatched concurrently across a thread pool.

(Feature: Monospace Preformatted Text / ASCII Diagram)

  Incoming Block Batch
           │
           ▼
┌──────────────────────┐
│ Address Parser & DAG │
└──────────┬───────────┘
           │
     ┌─────┴────────────────────────┐
     ▼                              ▼
[Tx 1 (Addr A, B)]           [Tx 2 (Addr D, E)]   <── No Conflict (In-Degree = 0)
     │ (Writes B)                   │ (Writes D)
     ▼                              ▼
[Tx 3 (Addr B, C)]           [Tx 4 (Addr D, F)]   <── Dependent (Queued)

3. Implementation Details

(Feature: Fenced Code Block with Syntax Highlighting)

Below is an excerpt from our custom Transaction Processor demonstrating deterministic state validation:

from sawtooth_sdk.processor.handler import TransactionHandler
from sawtooth_sdk.processor.exceptions import InvalidTransaction

class ParallelTransferHandler(TransactionHandler):
    @property
    def family_name(self):
        return 'parallel_transfer'

    @property
    def namespaces(self):
        return [self._namespace]

    def apply(self, transaction, context):
        """Execute state transition atomically."""
        header = transaction.header
        payload = self._decode_payload(transaction.payload)
        
        sender_address = header.inputs[0]
        receiver_address = header.outputs[0]
        
        # Verify disjoint address constraint
        if sender_address == receiver_address:
            raise InvalidTransaction("Self-transfers are invalid")
            
        current_balance = self._get_state(context, sender_address)
        if current_balance < payload['amount']:
            raise InvalidTransaction("Insufficient balance for transfer")
            
        # Commit state updates
        context.set_state({
            sender_address: current_balance - payload['amount'],
            receiver_address: self._get_state(context, receiver_address) + payload['amount']
        })

(Feature: Collapsible Disclosure Widget)

Click to view raw transaction payload schema (JSON)
{
  "family": "parallel_transfer",
  "version": "1.0",
  "inputs": [
    "1cf126a8027a442e92c5a083315a6b0c",
    "1cf126a8027a442e92c5a083315a6b0d"
  ],
  "outputs": [
    "1cf126a8027a442e92c5a083315a6b0d"
  ],
  "amount": 250
}

(Feature: Keyboard Input Tags)

To terminate the benchmark run during evaluation, send the interrupt signal via Ctrl + C in your terminal session.

# Execute 10,000 synthetic transactions across 8 worker cores
python3 benchmark_runner.py \
  --workload=uniform \
  --transactions=10000 \
  --batch-size=128 \
  --workers=8 \
  --output=results_parallel.json

4. Experimental Results & Benchmarks

(Feature: Formatted Data Table with Column Alignments)

We measured transaction throughput (Transactions Per Second — TPS) and average commit latency across varying worker core allocations and contention levels:

Worker CoresWorkload PatternSerial TPSParallel TPSLatency (ms)Speedup Factor
1 CoreUniform (Low Contention)42041848.2 ms1.00x
2 CoresUniform (Low Contention)42079826.1 ms1.90x
4 CoresUniform (Low Contention)4201,42015.3 ms3.38x
8 CoresUniform (Low Contention)4202,6509.8 ms6.31x
8 CoresSkewed Hotspot (90% Contention)42058038.5 ms1.38x

Key Observations

(Feature: Nested Unordered List)

  • Linear Scaling in Low-Contention Scenarios:
    • When transactions operate on independent state keys, throughput scales almost linearly up to 8 physical cores.
    • Memory bus synchronization remains below 12% overhead.
  • Contention Bottlenecks:
    • Under heavy hotspot skew (e.g. 90% of transactions modifying the same token balance), dependency graph edges serialize execution, demonstrating that parallel scheduling benefits are bound by workload partitioning.

5. Engineering Roadmap & Checklist

(Feature: Task / Checklists)

Here is the tracking status of our transaction engine optimizations:

  • Conflict Graph DAG generator with O(V+E)\mathcal{O}(V + E) topological resolution
  • Multi-threaded concurrent transaction dispatcher in Python & C++
  • Zero-copy memory state caching layer for high-throughput reads
  • Adaptive dynamic batching based on real-time address conflict heuristics
  • Hardware-accelerated cryptographic signature validation with SIMD/AVX-512

6. Conclusion & Summary

Deterministic transaction scheduling shifts concurrency arbitration from runtime locks to compile-time dependency graphs, enabling modern ledgers to scale with multicore server hardware. This study validated that decoupled state machines can achieve over 6x throughput gains on modern hardware when transactions explicitly declare input and output namespaces.


(Feature: Footnotes)

Footnotes

  1. ACID guarantees in distributed systems ensure Atomicity, Consistency, Isolation, and Durability across distributed consensus state updates. ↩