Exploring the architectural patterns needed to build a payment system handling 10,000 transactions per second without losing money, using practical analogies and real-world distributed systems concepts.
Building a Payment System That Never Loses Money
In the world of distributed systems, few problems are as critical as building payment systems that maintain perfect consistency. When handling thousands of transactions per second, the risk of double-spending, data corruption, or lost payments becomes a real business threat. Let's walk through the thought process of constructing such a system, starting with fundamental problems and building up to comprehensive solutions.
The Core Problem: Race Conditions in Payment Processing
Imagine a simple scenario: two customers attempt to withdraw the last $100 from an account simultaneously. Both requests check the balance, see $100 available, and both proceed with the withdrawal. Suddenly, $200 has been withdrawn from a $100 account.
This is a classic race condition, where multiple operations access shared data concurrently and produce inconsistent results. The specific variant where both operations read stale data before either writes is particularly dangerous in financial systems.
Solution: Pessimistic Locking
The straightforward approach is to implement locking mechanisms that serialize access to shared resources. In database terms, this means using row-level locks that prevent other transactions from modifying the same row until the lock is released.
In PostgreSQL, this is achieved with SELECT FOR UPDATE, which locks the selected rows until the transaction completes. The first transaction to acquire the lock processes successfully, while subsequent transactions wait until the lock is released. When the second transaction finally checks the balance, it finds insufficient funds and rejects the operation.
Trade-offs: Pessimistic locking provides strong consistency but can create bottlenecks. Under high contention, transactions may spend significant time waiting for locks, reducing throughput. It also increases the complexity of transaction management and requires careful handling of lock timeouts to avoid deadlocks.
Scaling Beyond a Single Server
A single server, no matter how powerful, has physical limits. The suya vendor analogy illustrates this well: even with a larger grill and better equipment, there's still only one person cooking, creating a queue of waiting customers.
Vertical Scaling
Vertical scaling involves adding more resources to a single server—more CPU, RAM, or faster storage. This approach can temporarily increase throughput but ultimately hits hard limits. The suya vendor can only get so big before becoming impractical.
Trade-offs: Vertical scaling simplifies system design and reduces coordination overhead, but it's expensive, creates single points of failure, and offers diminishing returns beyond certain thresholds.
Horizontal Scaling
Horizontal scaling involves adding more servers rather than making existing ones more powerful. The suya vendor opens multiple locations, each handling its own queue. The total capacity increases linearly with the number of locations.
Trade-offs: Horizontal scaling offers better scalability and fault tolerance but introduces significant complexity in data consistency, load distribution, and inter-node communication. It requires careful partitioning of data and requests to avoid creating new bottlenecks.
Separating Read and Write Workloads
In payment systems, read operations (checking balance, transaction history) often outnumber write operations (transferring money). If all operations hit the same database, read traffic can overwhelm write capacity.
Solution: Read Replicas
Implementing read replicas allows us to distribute read operations across multiple copies of the database while keeping write operations on the primary node. The suya vendor places an assistant to handle status checks while the main vendor processes new orders.
Trade-offs: Read replicas improve read performance and reduce load on the primary database, but they introduce eventual consistency between replicas and the primary. They also increase operational complexity and storage costs.
Limitations of Read Replicas
Read replicas alone don't solve write scaling. If 10,000 new transactions arrive per second, the primary database still becomes a bottleneck. To scale writes, we need a different approach.
Partitioning Data for Write Scaling
When write operations exceed the capacity of a single database, we need to partition the data across multiple databases. This is where sharding comes in.
Solution: Sharding
Sharding distributes data across multiple database nodes based on some partitioning key. For the payment system, we might partition accounts by customer ID or geographic region. The suya vendor directs customers 1-2,000 to location one, 2,000-4,000 to location two, and so on.
Trade-offs: Sharding enables near-linear scaling of write capacity but complicates application logic, as it requires routing requests to the appropriate shard. It also makes cross-shard operations more complex and can lead to data hotspots if the partitioning key isn't carefully chosen.
Handling Cross-Shard Transactions
What happens when a customer at one location needs to pay for someone at another location? The suya vendor at location one collects payment but can't immediately confirm with location three before releasing the order.
Solution: SAGA Pattern
The SAGA pattern breaks a distributed transaction into a sequence of local transactions, each with a compensating action for rollback. In our payment example:
- Location one debits the customer's account and records "owe location three one order"
- Location three fulfills the order
- If location three doesn't confirm within a timeout, location one automatically reverses the charge
Trade-offs: SAGAs provide resilience without requiring distributed locks, but they complicate error handling and can lead to inconsistent states during partial failures. They also require careful implementation of compensating transactions.
Distributing Traffic and Ensuring Availability
With multiple servers handling requests, we need intelligent traffic distribution and redundancy to prevent single points of failure.
Solution: Load Balancing
A load balancer sits in front of servers and distributes incoming traffic based on various algorithms (round-robin, least connections, etc.). The suya vendor places a host at the entrance who directs customers to the least busy location.
Trade-offs: Load balancing improves resource utilization and prevents any single server from being overwhelmed, but it adds another component that can fail and requires careful configuration to distribute traffic effectively.
High Availability and Failover
To prevent the load balancer itself from becoming a single point of failure, we implement redundancy. A backup load balancer stands ready to take over if the primary fails. This automatic switchover is called failover.
Trade-offs: High availability setups improve system resilience but increase complexity and cost. They also require synchronization between primary and failover components to avoid split-brain scenarios.
Rate Limiting
On top of load balancing, rate limiting protects the system from abuse by capping the number of requests a client can make within a time window. This prevents bad actors from overwhelming the system.
Trade-offs: Rate limiting protects the system but must be carefully configured to avoid blocking legitimate traffic. It also adds latency to each request as the system checks rate limits.
Observability at Scale
At 10,000 transactions per second, debugging failures becomes challenging. A single transaction might touch multiple services across dozens of servers, with logs scattered everywhere.
Solution: Correlation IDs and Distributed Tracing
Every request receives a unique correlation ID that follows it through the entire system. When something breaks, developers can search for this ID to see the complete journey of the request across all services.
Trade-offs: Correlation IDs dramatically improve debugging capabilities but require careful propagation across all services and can increase log volume. They also don't solve the underlying problems—they just make them easier to find.
Centralized Logging
Tools like the ELK stack (Elasticsearch, Logstash, Kibana) or Datadog aggregate logs from all servers into a searchable repository. Elasticsearch stores the logs, while Kibana provides a visualization interface.
Trade-offs: Centralized logging improves observability but introduces additional complexity and potential performance bottlenecks in the logging infrastructure itself. It also requires careful management of log retention and storage costs.
The Complete Architecture
Putting all these patterns together, a robust payment system architecture looks like this:
- Edge Layer: Rate limiting and DDoS protection
- Load Balancer: Distributes traffic across application servers with automatic failover
- Application Servers: Process transactions using pessimistic locking for critical sections
- Database Layer: Sharded across multiple nodes, with read replicas handling query traffic
- Cross-Shard Coordination: SAGA pattern for distributed transactions
- Observability Layer: Correlation IDs flowing through all services, with centralized logging
Trade-offs Summary
Each pattern introduces both benefits and limitations:
- Consistency vs. Availability: Strong consistency prevents data corruption but can reduce availability during failures
- Scalability vs. Complexity: Horizontal scaling enables growth but adds operational overhead
- Performance vs. Resource Usage: Read replicas improve performance but increase storage costs
- Resilience vs. Simplicity: SAGAs provide fault tolerance but complicate error handling
Building a payment system that never loses money requires careful balancing of these trade-offs based on specific business requirements. There's no single "right" solution—only the right combination of patterns for your particular use case.
The patterns discussed here form the foundation of financial-grade distributed systems. While the implementation details vary across technologies, the underlying principles remain consistent across payment processors, banking systems, and financial platforms worldwide.
For further reading on these patterns, explore resources on the CAP theorem, SAGA pattern implementation, and distributed tracing.

Comments
Please log in or register to join the discussion