Explain the four sources of distributed complexity, describe their concrete symptoms in Java/Spring systems, understand why some problems (like certain agreement under failures) have no perfect solution, map each source to mitigations covered later in the volume, and use this model to review designs
Distributed systems are hard because of four intertwined sources of complexity: the network is unreliable, timing is unpredictable, components suffer partial failure, and replicated data raises consistency questions.
Figure 1 — Sources of complexity (TOC)
TOC source
Definition
Network
Messages can be lost, delayed, duplicated, reordered or blocked by partitions; there is no way to know for certain whether a message arrived
Timing
No global clock; clocks drift and skew; delays and pauses (GC, VM migration) are unbounded, so "too slow" and "dead" look the same
Partial failure
Some components fail while others work, and a caller often can't tell whether an operation happened, failed or is still in progress
Consistency
Replicated or cached copies of data can diverge; deciding what readers see and how to reconcile updates requires deliberate models
Table 1 — The four TOC sources
2Why It Exists
Physical separation means information travels at finite speed over imperfect links, and machines fail independently. Some consequences are provably unavoidable: the Two Generals problem shows agreement over a lossy channel can't be guaranteed; FLP shows deterministic consensus is impossible in a fully asynchronous system with even one faulty process.
In practice these limits appear as everyday engineering problems rather than theorems. A timeout cannot tell whether a payment was taken, a two-second GC pause can let a lease expire while the process still believes it holds a lock, and a read replica a few hundred milliseconds behind can show stock that has already been sold. Because none of these can be eliminated, each design must decide which guarantees it needs and pay for them: idempotency keys for retries, monotonic clocks and fencing tokens for timing, timeouts and fallbacks for partial failure, and explicit consistency models for replicas. Naming the four sources gives teams a checklist for design reviews and a shared vocabulary for incidents, so problems are recognised as known categories rather than one-off surprises.
Figure 2 — Complexity → mitigation
3Core Concepts
3.1 Network
Figure 3 — Network (TOC): what can go wrong
Network behaviour
Symptom in Java
Loss
Timeouts, SocketTimeoutException
Delay
p99 spikes, pool exhaustion
Duplication
Same message processed twice after retry
Reordering
Events arrive out of sequence
Partition
Some nodes unreachable while others fine
Table 2 — How the network shows up
3.2 Timing
Figure 4 — Timing (TOC): clocks disagree
Timing hazard
Example
Clock skew
Node clocks differ by tens of ms
Clock jumps
NTP correction moves time backwards
Process pauses
Full GC for 2 s: lease expired while "working"
Unbounded delay
Message arrives after timeout decision
Table 3 — Why time can't be trusted
JAVA
// Measure durations with a monotonic clock, never wall-clock timelong start = System.nanoTime(); // monotoniccallPricing();Duration elapsed = Duration.ofNanos(System.nanoTime() - start);// Wall clock for business timestamps only — and inject it for testabilityInstant placedAt = Instant.now(clock);
3.3 Partial failure
Figure 5 — Partial failure (TOC): the ambiguous outcome
JAVA
// Idempotency key turns an ambiguous retry into a safe oneString key = "order-" + orderId + "-charge";PaymentResult r = paymentClient.charge(ChargeRequest.of(orderId, amount), Map.of("Idempotency-Key", key));// On timeout: retry with the same key — the PSP returns the original result
DB isolation/replication settings, versions, Spring Modulith events
Table 8 — Toolkit
6Practical Examples
6.1 Double charge
A checkout retried a payment after a 2-second timeout; the first attempt had succeeded. Customers were double-charged until idempotency keys were added.
6.2 GC pause lease
A scheduler held a 10-second lock lease; a 12-second GC pause let another instance take the lock, and both ran the nightly settlement. Fencing tokens (Chapter 6) fixed it.
6.3 Worked example — complexity inventory
Operation
Network
Timing
Partial
Consistency
Place order
✔
—
✔
✔
Nightly job
—
✔
✔
—
Table 9 — Assess per operation
6.4 Worked example — Two Generals intuition
Attempt
Problem
Send ack of ack…
Last message can always be lost
Table 10 — No perfect agreement over lossy links
6.5 Worked example — duplicate detection
Mechanism
Where
Idempotency key table
Payment service
Table 11 — Handle network duplication
6.6 Worked example — ordering
Need
Mechanism
Order events per order
Kafka key = orderId
Table 12 — Handle reordering
6.7 Worked example — clock skew impact
Use
Safe?
Display timestamps
✔
Decide "latest write" across nodes
✘
Table 13 — Choose carefully
6.8 Worked example — timeout vs dead
Observation
Possible truth
No reply in 2 s
Slow, dead, or network issue
Table 14 — Ambiguity
6.9 Worked example — replica lag measurement
SQL
SELECT client_addr, replay_lag FROM pg_stat_replication;
6.10 Worked example — partial failure test
Chaos test
Expectation
Kill pricing pods
Checkout shows cached prices
Table 15 — Test before production does
6.11 Worked example — monotonic time bug
Bug
Fix
Timeout computed with currentTimeMillis() jumped
Use nanoTime()
Table 16 — Timing hygiene
6.12 Worked example — consistency decision
Data
Model
Stock for checkout
Strong
Stock on listing
Eventual
Table 17 — Per use
6.13 Worked example — failure model assumption
Component
Assumed model
Kafka broker
Crash-recovery
Table 18 — Algorithms depend on it
6.14 Worked example — cross-region consensus cost
Setup
Commit latency
3 AZs in Mumbai
~5 ms
Mumbai + Singapore + Frankfurt
~150 ms
Table 19 — Geography × coordination
6.15 Worked example — observability for complexity
Signal
Complexity revealed
Retry counts
Network
Replica lag
Consistency
GC pause metrics
Timing
Table 20 — Measure the sources
6.16 Worked example — reorder-safe consumer
Technique
Detail
Version check
Ignore older versions
Table 21 — Tolerate reordering
6.17 Worked example — cache invalidation
Approach
Staleness
TTL 60 s
Up to 60 s
Event-driven eviction
Seconds
Table 22 — Consistency choices
6.18 Worked example — chaos engineering
Tool
Fault
Toxiproxy
Latency, resets
AWS FIS
AZ network disruption
Table 23 — Inject each source
6.19 Worked example — design review template
Question
Source
What if the message is lost/duplicated?
Network
What if the node pauses 10 s?
Timing
What if half the deps fail?
Partial
What do readers see during lag?
Consistency
Table 24 — Four-lens review
6.20 Worked example — mapping to later chapters
Source
Chapter
Network
2, 7
Timing
5
Consistency
3, 4
Table 25 — Roadmap
6.21 Worked example — interview answer
Source
Example
Network
Lost responses
Timing
Clock skew, GC pause
Partial failure
Unknown outcome
Consistency
Replica lag
Table 26 — Structured answer
7Common Problems & Mistakes
Wall-clock time for timeouts or ordering.
Retries without idempotency.
Assuming bounded delays.
Ignoring replica lag.
Treating timeout as failure.
No chaos testing.
These mistakes usually come from reasoning about a distributed system as if it were one process. Wall-clock time looks like a reliable timer until NTP moves it backwards and a timeout fires early or never. Retries look harmless until a lost response turns one payment into two, which is why every retried operation with side effects needs an idempotency key. Assuming bounded delays leads to locks and leases that break during GC pauses. Reading from replicas without considering lag shows users stale stock or orders that seem to have vanished. Treating a timeout as a definite failure triggers compensations for operations that actually succeeded. Finally, without chaos testing with tools such as Toxiproxy or AWS FIS, these failure modes are discovered in production rather than in a controlled experiment.
Four intertwined sources of complexity that do not exist inside one process. The network is unreliable: messages can be lost, delayed, duplicated, reordered or blocked by partitions, and you can never be sure a message arrived. Timing is unpredictable: there is no global clock, clocks drift and jump, and pauses such as garbage collection make a slow node look dead. Partial failure means some components fail while others keep working, and a caller often cannot tell whether an operation happened. Consistency becomes a choice, because replicas and caches diverge. These are inherent properties, not bugs to fix once, so the architect's job is to choose the guarantees needed and pay for them knowingly.
Q2BasicTricky
Why is a timeout ambiguous?
A timeout only tells you that no response arrived in time; it says nothing about what happened on the other side. The request may have been lost before reaching the server, the server may still be processing it, for example during a GC pause or behind a slow database, or it may have completed successfully with the response lost on the way back. From the caller's side these cases look identical. That is why treating a timeout as a definite failure is dangerous: in the note's double-charge example, a payment retried after two seconds had in fact succeeded. Safe handling means idempotent retries, status queries or reconciliation rather than blind compensation.
Q3IntermediateCode
Why shouldn't you use wall-clock time for measuring timeouts?
Because wall-clock time can jump, so durations computed from it are unreliable. System.currentTimeMillis and Instant.now follow the system clock, which NTP, Chrony or an operator can move forwards or backwards, and virtual machine migrations can make it leap. If the clock moves back during a request, a timeout computed from wall-clock differences may fire late or never; a forward jump fires it early. For elapsed time use a monotonic clock, System.nanoTime in Java, which only moves forwards within a JVM, as the note's example shows. Keep wall-clock time for business timestamps, inject a java.time.Clock for testability, and never use it to order events across nodes.
Q4IntermediateDesign
How do you make retries safe?
Make the operation idempotent, so performing it twice has the same effect as performing it once. For calls with side effects, such as charging a card, the client generates an idempotency key tied to the business operation, for example the order id plus charge, and sends it with every attempt. The server records the key with the result and, on a repeated key, returns the stored result instead of charging again. Message consumers do the same with an event id or entity version table. Combine this with bounded retries, exponential backoff with jitter and a retry budget, so retries do not amplify an outage, and only retry operations known to be safe.
Q5Senior EngineerConcept
What is the Two Generals problem?
The Two Generals problem is a thought experiment showing that two parties cannot be certain they agree when communicating over a channel that may lose messages. One general sends an attack time by messenger; the other replies with an acknowledgement, but the sender cannot know the acknowledgement arrived, so it needs an acknowledgement of the acknowledgement, and so on forever. Whatever the last message is, it can be lost. The practical lesson is that no protocol can guarantee agreement over a lossy network, so real systems settle for practical certainty instead: retries with idempotency, timeouts and reconciliation. It is the same reason an HTTP timeout leaves the outcome of a request unknown.
Q6Senior EngineerInternals
What is the partially synchronous model?
It is a system model that sits between the synchronous model, where message delays and clock drift have known bounds, and the asynchronous model, which assumes no timing bounds at all. In a partially synchronous system the bounds exist but hold only most of the time, or eventually after periods of instability. This matters because FLP shows deterministic consensus is impossible in a purely asynchronous system with even one faulty process. Raft and Paxos assume partial synchrony: they always stay safe, never deciding two different values, but only make progress when timing behaves, for example electing a leader once heartbeats arrive reliably. Real networks, and clusters such as etcd, approximate this model.
Q7Technical ArchitectTricky
How do GC pauses cause correctness bugs?
A paused process does not know it was paused, so after waking it acts on assumptions that are no longer true. A stop-the-world GC, VM migration or heavy swapping can freeze a JVM for seconds. In the note's example a scheduler held a 10-second lock lease, paused for 12 seconds, and another instance took the lock, so both ran the nightly settlement. The same happens with leader leases and cached tokens. Checking the lease before writing does not help, because the pause can happen right after the check. The robust fix is a fencing token: the lock service issues an increasing number with each lease, and storage rejects writes carrying an older token.
Q8Solution ArchitectScenario
How do you communicate distributed complexity to stakeholders?
I avoid theory and start with incidents they recognise. Each of the four sources gets one concrete story: a lost response that double-charged customers, a GC pause that ran a batch job twice, a partial outage where checkout failed because a minor dependency was down, and replica lag that showed sold-out stock as available. Then I show what each guarantee costs, such as idempotency work, extra latency for strong consistency, or cross-region coordination at around 150 ms per commit. Finally I tie choices to business requirements: strong consistency for payments and stock reservation, eventual consistency for listings and analytics. Framing it as risk, cost and customer impact lets stakeholders choose guarantees knowingly.
Newsletter
Get new tutorials every week
New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.
Your subscription could not be saved. Please try again.
Almost done! Check your inbox and click the confirmation link to complete your subscription.