ArchitectMind
  1. Home
  2. Tutorials
  3. Book 7 · Application Architecture & Distributed Systems
  4. Volume IV
  5. Distributed System Complexity
Book 7 · Volume IV — Distributed Systems Fundamentals · Topic 1.3

Distributed System Complexity

Network, timing, partial failure and consistency

9 min readStage 3 · Systems8 interview questions
Chapter
Chapter 1 — Distributed Systems
Prerequisites
Topics 1.1, 1.2
You will be able to
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
Covers
Network; Timing; Partial failure; Consistency
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts
  4. 4. How It Works Internally — System Models
  5. 4A. Deep Dive — Numbers That Shape Design
  6. 5. APIs / Features / Syntax
  7. 6. Practical Examples
  8. 7. Common Problems & Mistakes
  9. 8. Performance & Production Considerations
  10. 9. Architecture & Design Considerations
  11. 10. Interview Questions & Answers

1Concept / Definition

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.

Sources of complexity (TOC)
Figure 1 — Sources of complexity (TOC)
TOC sourceDefinition
NetworkMessages can be lost, delayed, duplicated, reordered or blocked by partitions; there is no way to know for certain whether a message arrived
TimingNo global clock; clocks drift and skew; delays and pauses (GC, VM migration) are unbounded, so "too slow" and "dead" look the same
Partial failureSome components fail while others work, and a caller often can't tell whether an operation happened, failed or is still in progress
ConsistencyReplicated 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.

Complexity → mitigation
Figure 2 — Complexity → mitigation

3Core Concepts

3.1 Network

Network (TOC): what can go wrong
Figure 3 — Network (TOC): what can go wrong
Network behaviourSymptom in Java
LossTimeouts, SocketTimeoutException
Delayp99 spikes, pool exhaustion
DuplicationSame message processed twice after retry
ReorderingEvents arrive out of sequence
PartitionSome nodes unreachable while others fine
Table 2 — How the network shows up

3.2 Timing

Timing (TOC): clocks disagree
Figure 4 — Timing (TOC): clocks disagree
Timing hazardExample
Clock skewNode clocks differ by tens of ms
Clock jumpsNTP correction moves time backwards
Process pausesFull GC for 2 s: lease expired while "working"
Unbounded delayMessage arrives after timeout decision
Table 3 — Why time can't be trusted
JAVA
// Measure durations with a monotonic clock, never wall-clock time
long start = System.nanoTime();          // monotonic
callPricing();
Duration elapsed = Duration.ofNanos(System.nanoTime() - start);
 
// Wall clock for business timestamps only — and inject it for testability
Instant placedAt = Instant.now(clock);

3.3 Partial failure

Partial failure (TOC): the ambiguous outcome
Figure 5 — Partial failure (TOC): the ambiguous outcome
JAVA
// Idempotency key turns an ambiguous retry into a safe one
String 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

3.4 Consistency

Consistency (TOC): replicas disagree
Figure 6 — Consistency (TOC): replicas disagree
Consistency situationExample
Replica lagRead replica shows old stock
Cache stalenessRedis has old price
Concurrent updatesTwo admins edit the same product
Cross-service copiesOrder service's customer copy outdated
Table 4 — Where divergence appears

4How It Works Internally — System Models

System modelAssumptionRealism
SynchronousKnown bounds on delay and clock driftUnrealistic for general networks
AsynchronousNo timing bounds at allToo pessimistic; consensus impossible (FLP)
Partially synchronousBounds hold most of the timeRealistic; basis of Raft/Paxos
Table 5 — Models behind distributed algorithms
Failure modelMeaning
Crash-stopNode stops forever
Crash-recoveryNode stops, restarts with durable state
OmissionMessages dropped
ByzantineArbitrary/malicious behaviour (blockchains)
Table 6 — What kinds of faults we assume

4ADeep Dive — Numbers That Shape Design

Latency numbers (approximate)
Figure 7 — Latency numbers (approximate)
Design implicationBecause
Avoid chatty cross-service callsEach hop costs ~1 ms+ and adds failure
Cache close to useMemory is ~10,000× faster than a network hop
Keep consensus within a regionCross-region RTT ~100 ms per round
Batch writesAmortise round trips
Table 7 — Latency drives architecture

5APIs / Features / Syntax

ComplexityJava/Spring tooling
NetworkResilience4j timeouts/retries, HTTP client timeouts
TimingSystem.nanoTime, injected Clock, Chrony/Time Sync
Partial failureIdempotency keys, circuit breakers, fallbacks
ConsistencyDB 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

OperationNetworkTimingPartialConsistency
Place order✔—✔✔
Nightly job—✔✔—
Table 9 — Assess per operation

6.4 Worked example — Two Generals intuition

AttemptProblem
Send ack of ack…Last message can always be lost
Table 10 — No perfect agreement over lossy links

6.5 Worked example — duplicate detection

MechanismWhere
Idempotency key tablePayment service
Table 11 — Handle network duplication

6.6 Worked example — ordering

NeedMechanism
Order events per orderKafka key = orderId
Table 12 — Handle reordering

6.7 Worked example — clock skew impact

UseSafe?
Display timestamps✔
Decide "latest write" across nodes✘
Table 13 — Choose carefully

6.8 Worked example — timeout vs dead

ObservationPossible truth
No reply in 2 sSlow, 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 testExpectation
Kill pricing podsCheckout shows cached prices
Table 15 — Test before production does

6.11 Worked example — monotonic time bug

BugFix
Timeout computed with currentTimeMillis() jumpedUse nanoTime()
Table 16 — Timing hygiene

6.12 Worked example — consistency decision

DataModel
Stock for checkoutStrong
Stock on listingEventual
Table 17 — Per use

6.13 Worked example — failure model assumption

ComponentAssumed model
Kafka brokerCrash-recovery
Table 18 — Algorithms depend on it

6.14 Worked example — cross-region consensus cost

SetupCommit latency
3 AZs in Mumbai~5 ms
Mumbai + Singapore + Frankfurt~150 ms
Table 19 — Geography × coordination

6.15 Worked example — observability for complexity

SignalComplexity revealed
Retry countsNetwork
Replica lagConsistency
GC pause metricsTiming
Table 20 — Measure the sources

6.16 Worked example — reorder-safe consumer

TechniqueDetail
Version checkIgnore older versions
Table 21 — Tolerate reordering

6.17 Worked example — cache invalidation

ApproachStaleness
TTL 60 sUp to 60 s
Event-driven evictionSeconds
Table 22 — Consistency choices

6.18 Worked example — chaos engineering

ToolFault
ToxiproxyLatency, resets
AWS FISAZ network disruption
Table 23 — Inject each source

6.19 Worked example — design review template

QuestionSource
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

SourceChapter
Network2, 7
Timing5
Consistency3, 4
Table 25 — Roadmap

6.21 Worked example — interview answer

SourceExample
NetworkLost responses
TimingClock skew, GC pause
Partial failureUnknown outcome
ConsistencyReplica 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.

8Performance & Production Considerations

AspectGuideline
NetworkTimeouts, retries, idempotency
TimingMonotonic clocks, synced wall clocks, fencing
Partial failureFallbacks, breakers, bulkheads
ConsistencyExplicit models, lag monitoring
ValidationChaos experiments
Table 27 — Operational guidelines

9Architecture & Design Considerations

  • Review designs through the four lenses.
  • Accept impossibility results; design pragmatic guarantees.
  • Choose system and failure models consciously.
  • Let latency numbers guide boundaries.

10Interview Questions & Answers

Q1BasicConcept

What makes distributed systems hard?

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.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 7

Application Architecture & Distributed Systems

Volume I · Topic 1.1

Software Architecture

Architecture Fundamentals & Architectural Thinking

Volume I · Topic 1.2

Architecture Responsibilities

Architecture Fundamentals & Architectural Thinking

Volume I · Topic 1.3

Architect vs Developer

Architecture Fundamentals & Architectural Thinking

Volume II · Topic 1.1

Monolith Fundamentals

Application Architecture Styles & Patterns

Volume II · Topic 1.2

Types of Monoliths

Application Architecture Styles & Patterns

Volume II · Topic 1.3

Monolith Advantages

Application Architecture Styles & Patterns

Volume III · Topic 1.1

Domain-Driven Design

Domain-Driven Design & Business Boundaries

Volume III · Topic 1.2

Strategic vs Tactical DDD

Domain-Driven Design & Business Boundaries

Volume III · Topic 1.3

Domain Complexity

Domain-Driven Design & Business Boundaries

Volume IV · Topic 1.1

What Is a Distributed System?

Distributed Systems Fundamentals

Volume IV · Topic 1.2

Why Distributed Systems Exist

Distributed Systems Fundamentals

Volume V · Topic 1.1

What Makes a Microservice?

Microservices Architecture

Volume V · Topic 1.2

Service Granularity

Microservices Architecture

Volume V · Topic 1.3

Microservice Boundaries

Microservices Architecture

Volume VI · Topic 1.1

Event Fundamentals

Event-Driven & Asynchronous Architecture

Volume VI · Topic 1.2

Event Types

Event-Driven & Asynchronous Architecture

Volume VI · Topic 1.3

Event-Carried State

Event-Driven & Asynchronous Architecture

Volume VII · Topic 1.1

Scalability

Resilience, Scalability & Distributed Data

Volume VII · Topic 1.2

Stateless Architecture

Resilience, Scalability & Distributed Data

Volume VII · Topic 1.3

Load Balancing

Resilience, Scalability & Distributed Data

Volume VIII · Topic 1.1

ADR

Architecture Decision-Making & System Evolution

Volume VIII · Topic 1.2

Architecture Decision Process

Architecture Decision-Making & System Evolution

Volume VIII · Topic 1.3

Reversible vs Irreversible Decisions

Architecture Decision-Making & System Evolution