Articles

System Design: High-Volume Transaction Processing

Learn how to architect systems capable of handling tens of thousands of state-changing writes per second. This guide explores the critical trade-offs between throughput and correctness, including sharding, idempotency, and the use of event logs.

Written by:
APin

Senior Technology Analyst • Verified Expert

More from this author
System Design: High-Volume Transaction Processing

Learn how to architect systems capable of handling tens of thousands of state-changing writes per second. This guide explores the critical trade-offs between throughput and correctness, including sharding, idempotency, and the use of event logs.

The Challenges of High-Volume Transaction Systems

Designing for high-volume transaction processing shifts the primary engineering concern from simple feature implementation to rigorous throughput and correctness management. In systems processing tens of thousands of state-changing writes per second, traditional architectural assumptions regarding concurrency and failure handling often fail. At these volumes, developers cannot rely on hardware scaling to mask inefficient synchronization or poor locking strategies.

The primary technical shift involves acknowledging that rare race conditions, which appear negligible at low throughput, manifest as frequent, load-bearing failure states at scale. An edge case occurring once in 100,000 requests—barely a consideration at 10 transactions per second (TPS)—will trigger roughly twice every second at 50,000 TPS. Consequently, architectural correctness must be prioritized over vertical scaling.

To maintain integrity under load, engineers must implement the following design patterns:

  • Idempotency: Because network partitions and timeouts are inevitable, the system must treat duplicate request delivery as expected behavior. Every transaction requires an idempotency key to ensure operations are applied exactly once, regardless of retry attempts.
  • Aggregate Sizing: Keeping the lock footprint minimal is essential. Rather than locking multiple entities in a single unit of work—which introduces massive contention—architects should keep aggregate boundaries small, ensuring single-partition updates remain the common, high-speed path.
  • Append-Only Event Logs: Mutable state tables are insufficient for auditing and recovery. Utilizing an immutable event log serves as the single source of truth. By partitioning this log by entity (e.g., account ID), the system enforces strict ordering per account while maintaining the durability required to rebuild derived state if corruption occurs.
  • Backpressure and Load Shedding: When demand exceeds capacity, the system must gracefully degrade. Implementing explicit load shedding ensures the core transaction path remains stable, protecting the system from cascading failures that occur when resource queues become saturated.

Ultimately, high-volume systems succeed by optimizing the "hot path" for local, shard-isolated operations and relegating complex coordination—such as multi-shard sagas—to secondary processes. By treating throughput as a constraint that forces explicit concurrency control, engineers move from defensive, hardware-dependent scaling to robust, deterministic architectural design.

Domain Modeling and Aggregate Boundaries

Domain‑Driven Design (DDD) recommends modeling a transaction as its own aggregate root, distinct from the account aggregates that hold balances. The evidence shows that the Transaction class encapsulates state transitions (Apply, Fail) and raises domain events, while each Account aggregate is updated only as a side‑effect of those events. This separation keeps the write‑set of a single command to a single row, eliminating the need to lock two account rows in the same unit of work.

When an aggregate spans multiple entities—e.g., a transfer that directly mutates both source and destination accounts—the database must acquire locks on both rows. At high throughput, such “hot‑row” contention becomes a bottleneck, as the evidence notes that a locking strategy that works at a few hundred TPS can collapse at tens of thousands of writes per second. By keeping the aggregate boundary small, each transaction write touches only the Transaction row, allowing the subsequent balance updates to be performed asynchronously (e.g., via an event log or outbox pattern).

  • Small aggregate boundary: one row lock per command, reducing lock duration and contention.
  • Separate aggregates: Transaction handles lifecycle; Account aggregates receive balance adjustments through domain events.
  • Concurrency control: only the transaction row is locked; account rows are updated in a later, eventually consistent step, which can be sharded by account_id for further isolation.

Practical implementation often follows the pattern shown in the evidence:

public class Transaction // aggregate root
{
    public TransactionId Id { get; }
    public AccountId FromAccount { get; }
    public AccountId ToAccount { get; }
    public Money Amount { get; }
    public TransactionStatus Status { get; private set; }

    public void Apply()
    {
        if (Status != TransactionStatus.Pending) throw new InvalidOperationException();
        Status = TransactionStatus.Applied;
        _domainEvents.Add(new TransactionAppliedEvent(Id, FromAccount, ToAccount, Amount));
    }
}

After Apply publishes TransactionAppliedEvent, a saga or consumer reads the event from the append‑only log (Kafka, Pulsar) and updates each Account aggregate in its own partition. This design satisfies the high‑volume requirement that every write be isolated, ordered, and idempotent, while preserving the correctness guarantees of DDD.

The Event Log as the Source of Truth

An immutable, append‑only event log is the single source of truth for any system that must guarantee durability and auditability. By writing each state‑changing request as a new record—never updating or deleting prior entries—the log preserves a complete, ordered history that can be replayed to reconstruct the system at any point in time. This property satisfies compliance frameworks such as SOC 2, ISO 27001, and NIST SP 800‑53, which require tamper‑evident records for forensic analysis and evidence of control effectiveness.

In practice the log is often implemented with a distributed streaming platform (e.g., Kafka or Pulsar) rather than a traditional relational table. A typical schema mirrors the example from the evidence:

CREATE TABLE transaction_log (
    sequence_id   BIGINT PRIMARY KEY,
    transaction_id UUID   NOT NULL,
    account_id    UUID   NOT NULL,   -- partition key for strict ordering
    amount_minor_units BIGINT NOT NULL,
    currency      CHAR(3) NOT NULL,
    created_at    TIMESTAMPTZ DEFAULT now()
);

Each accepted transaction is first durably appended to this log, partitioned by account_id so that all events for a single account appear in strict order. Downstream services consume the log via an outbox or CDC pattern, eliminating dual‑write anomalies.

Derived state—such as an account balance—must never be stored as an independent authoritative value. Instead it is a projection that can be recomputed by folding over the log:

SELECT SUM(amount_minor_units) AS current_balance
FROM transaction_log
WHERE account_id = '…';

Because the projection is recomputable, any corruption in a cached or materialized balance can be detected and corrected by re‑processing the log. This approach provides:

  • Auditability: every change is traceable to an immutable event.
  • Durability: the log can be replicated across nodes to survive hardware failures.
  • Recoverability: a broken projection can be rebuilt without external data.
  • Compliance alignment: immutable logs satisfy regulatory evidence requirements.

Performance optimizations—such as materialized views or in‑memory caches—are acceptable only when they are treated as read‑only replicas of the log’s truth. Any write path must go through the append‑only log; otherwise the system risks drift between the authoritative event stream and the derived state, undermining both correctness and auditability.

Idempotency and Consistency at Scale

In an at‑least‑once delivery model the transport layer may retransmit a request until it receives an acknowledgement. The application therefore must guarantee that processing the same request multiple times does not change the observable state – this is the definition of idempotency. When the business requirement is “exactly‑once effect”, idempotency becomes the only reliable way to bridge the gap between the unreliable delivery semantics and the need for a single, correct outcome.

High‑volume transaction systems illustrate the problem. A typical flow is:

Client → API Gateway → [idempotency check, rate limit] → Transaction Router → Shard (local ACID write) → Event Log (Kafka) → Outbox → downstream consumers

Because the event log is the immutable source of truth, every write must be appended exactly once. If a client retries a failed request, the router must detect that the request has already been recorded and skip the second write; otherwise the same TransactionId would be applied twice, violating the aggregate’s invariant that a transaction can transition from Pending to Applied only once.

  • Deterministic identifiers – a globally unique TransactionId (e.g., a UUID) is generated by the client or the gateway and persisted with the first successful write.
  • Idempotency key store – a fast key‑value table (e.g., Redis or a dedicated DB table) maps the key to the log offset or result metadata, enabling O(1) duplicate detection.
  • Replay‑safe processing – business logic must be written so that re‑executing the same event (e.g., applying a debit) leaves the system unchanged after the first execution.

Practical example: a money‑transfer API receives a request with header Idempotency-Key: abc123. The service checks the key store; if absent, it writes the transaction to the Kafka partition keyed by account_id, records the offset under abc123, and returns success. A subsequent retry finds the key, reads the stored offset, and returns the original response without writing a second event.

Because high‑throughput workloads turn rare race conditions into constant failures (a race that occurs once per 100 000 requests fires twice per second at 50 000 TPS), idempotency cannot be an optional defensive measure – it is a non‑negotiable design element. Without it, duplicate writes would corrupt aggregates, break the “exactly‑once” guarantee, and force costly compensating transactions.

Design recommendations:

  • Make the idempotency key part of the public contract and enforce it at the API gateway.
  • Persist the key together with the event log entry to guarantee atomicity.
  • Treat derived state (e.g., cached balances) as a projection of the immutable log; recompute or reconcile on detection of duplicate processing.

Scaling Throughput: Sharding and Coordination

Sharding divides the logical data set into independent partitions, each owned by a distinct database instance or node. By using a partition key that aligns with the most frequent access pattern—often the account identifier in a financial system—writes and reads are confined to a single shard, eliminating cross‑node locking and allowing each shard to sustain its own write‑ahead log (e.g., a Kafka partition). This “single‑partition path” handles the overwhelming majority of transactions, as described in the high‑volume design guide, and is the primary lever for scaling throughput.

When a transaction spans multiple shards (for example, a transfer between accounts on different partitions), the system must coordinate state changes without sacrificing the exactly‑once guarantee. The saga pattern provides a lightweight, asynchronous choreography:

  • Start step: Append a “transfer‑initiated” event to the event log, partitioned by the source account.
  • Local step: Apply the debit on the source shard and emit a “debit‑applied” event.
  • Compensating step: If the credit on the destination shard fails, a compensating “debit‑reversed” event is published to roll back the source.
  • Completion step: Once both sides acknowledge success, a “transfer‑completed” event marks the saga as finished.

Key implementation details that keep the saga efficient include:

  • Idempotent handlers that ignore duplicate events, ensuring safety under at‑least‑once delivery.
  • Per‑shard ordering guarantees provided by the underlying log, so each shard processes its own events sequentially.
  • Minimal synchronous coupling: only the originating service waits for the initial log write; all subsequent steps proceed asynchronously.

Optimizing for the common case while still supporting cross‑shard coordination involves:

  • Hot‑path routing: A transaction router inspects the partition keys and forwards same‑shard operations directly to the local ACID write path, bypassing the saga engine.
  • Back‑pressure propagation: If a shard signals overload, the router throttles incoming requests before they enter the saga, preventing cascade failures.
  • Selective materialized views: Cached balances are derived from the immutable event log and refreshed only for shards that experience high read volume, preserving the log as the source of truth.

By keeping the aggregate boundary small (e.g., a Transaction aggregate separate from Account aggregates) and confining most traffic to a single partition, the system reduces lock contention and maximizes parallelism. The saga pattern then provides a reliable, observable path for the minority of cross‑shard operations, ensuring correctness without compromising the high‑throughput goals of the overall architecture.

Operational Resilience and Overload Protection

In high‑throughput transaction systems, stability under load depends on three tightly coupled mechanisms: backpressure, load shedding, and observability. Each addresses a different point in the request‑processing pipeline, and together they prevent a surge of work from exhausting CPU, memory, or I/O resources.

Backpressure

Backpressure is a flow‑control signal that propagates upstream when a downstream component cannot keep up. By exposing a ready or capacity indicator, producers can throttle or pause emission of new work instead of queuing indefinitely.

  • Reactive streams: libraries such as Project Reactor or RxJava implement the request(n) protocol, allowing a consumer to request only the number of items it can process.
  • Network level: TCP window size and HTTP/2 flow control automatically apply backpressure to client connections.
  • Message brokers: Kafka partitions can be configured with max.poll.records so a consumer fetches a bounded batch, preventing memory blow‑up.

Load Shedding

When backpressure alone cannot keep the system within safe limits—e.g., a sudden spike that would saturate the thread pool—load shedding discards or de‑prioritizes work before it reaches critical sections.

  • Circuit breakers (e.g., Hystrix, Resilience4j) open when error rates or latency exceed thresholds, returning immediate failures to callers.
  • Rate limiting at the API gateway rejects excess requests with HTTP 429, preserving capacity for in‑flight transactions.
  • Priority queues can drop low‑priority events (such as analytics telemetry) while preserving core financial writes.

Observability

Effective overload protection requires real‑time insight into system behavior. Observability combines metrics, distributed tracing, and structured logs to surface the health of each stage.

  • Metrics: expose counters for request rate, queue depth, and backpressure signals; set alerts on sudden spikes.
  • Tracing: propagate trace IDs through the event log (Kafka) so latency anomalies can be pinpointed to a specific service or partition.
  • Logs: emit structured entries when backpressure is applied or load is shed, enabling post‑mortem analysis.

Practical implementation example: an API gateway performs an idempotency check, then forwards the request to a transaction router. The router reads max.poll.records from Kafka, processes a bounded batch, and reports queue_length metrics. If the metric exceeds a configured threshold, the router signals backpressure to the gateway and activates a circuit breaker that returns 503 Service Unavailable for new requests.

By coupling backpressure signals, disciplined load shedding, and continuous observability, engineers can maintain correctness (exactly‑once processing) while protecting the system from overload‑induced failures.

Editorial Policy & Research Methodology

Our findings are based on rigorous internal research, verified industry benchmarks, and direct technical implementation experience from our enterprise client projects. All statistics and technical claims are reviewed by senior engineers before publication to ensure accuracy, transparency, and helpfulness for our readers.

Have an Idea?

Let's Build Something Amazing Together.