Distributed Systems
This is Vb within Act V — Connection. Va laid the wire and the protocols that move bytes between machines. Here we ask what it costs to actually coordinate across that wire. The whole chapter rests on three facts the network forces on you. Messages take time. Clocks drift. Failures are partial — one node alive, one dead, and from the survivor's seat you cannot tell which case you are in. Every section below is a consequence of those three facts and the compromises they force.
The moment you have two computers
A program running on one machine has a small, comforting set of guarantees. There is one clock, so if instruction A runs before instruction B in the source, A also happens before B in time. Memory is shared, so once a write commits, every later read sees it. Failure is total — when the process dies, nothing is running. Mutexes, channels, and atomics all sit on top of those three facts.
Add a network and the three facts evaporate at once.
Messages take time. A same-rack round trip is about 0.1 ms; cross-availability-zone is about 1 ms; cross-region is 30–150 ms; satellite is roughly 600 ms. Every coordination decision pays this latency.
Clocks drift. NTP holds two well-tuned servers within a few milliseconds. Unsynchronized hardware clocks drift on the order of tens of microseconds per second — seconds per day. Worse, a leap second or misconfigured daemon can run a clock backwards.
Failure becomes partial. One node crashed, one survived, and the survivor cannot distinguish "peer is dead" from "peer is alive, network is slow" from "peer is alive, a router three hops away is dropping our packets". A timeout is the only tool you have, and any timeout you pick is wrong somewhere: too short and you false-positive a healthy peer and trigger a needless failover; too long and you block writes for the duration.
The hard part is not any one of these problems. The hard part is that they overlap. A stale read could be a genuinely old value, a clock that ran backwards, a reordered message, a recovering peer, or a partition that has been routing your traffic to a stale replica for an hour. The rest of this chapter is the vocabulary for talking about those overlapping ambiguities.
Fallacies developers reach for
In 1994 Peter Deutsch wrote down the list below; James Gosling added the eighth in 1997. Each entry is the conviction that an engineering trade-off can be ignored. Each shows up in production in a recognizable way.
Two examples make the pattern concrete. Latency is zero fails the moment a chain of synchronous RPCs (service A calls B calls C calls D) ties their tail latencies together. At a 1% timeout rate per hop, four hops in series produce roughly a 4% top-line failure rate even when each individual call is "fast". Topology doesn't change fails when an autoscaler replaces a node and the cached DNS entry on a peer still points at the old IP for the TTL window. Engineers don't violate these assumptions on purpose; they violate them by writing code that quietly assumes the network is a function call.
Pitfall — retries amplify failure. The common reaction to "the network is unreliable" is to retry. Naive retries (no backoff, no jitter, no idempotency keys) turn a transient blip into a self-inflicted DoS: every dependent service retries the same failed call simultaneously, the upstream fails harder, the retry storm compounds. Exponential backoff with full jitter, plus idempotency tokens, is the minimum competent answer.
Time and ordering
Distributed algorithms constantly need to ask "did event A happen before event B?". Wall-clock timestamps cannot answer reliably — clocks on different machines disagree, and one might even run backwards. Logical clocks replace physical time with a counter that orders events by the message structure of the computation itself.
Lamport clocks are the simplest version. Each node keeps a single integer counter. Three rules:
- Bump the counter on every local event.
- Attach the counter to every outgoing message.
- On receiving a message with timestamp
t, setcounter = max(counter, t) + 1.
That's it. The guarantee: if event A causally preceded event B, then LC(A) < LC(B). Two events with no causal relationship may end up with equal or out-of-order numbers.
To distinguish concurrent events from causally ordered ones, you need more state. Vector clocks give every node a counter per peer. On a local event a node bumps its own slot; on receive, it takes the elementwise max and bumps its own slot. Two vectors are comparable only when one dominates the other componentwise — otherwise the events were genuinely concurrent. Riak surfaces concurrent versions to the application using vector clocks; the cost is O(N) state per event for N writers.
[1,0,0] happens-before [2,2,0] (every slot is ≤, at least one strictly less). But [2,0,0] and [0,1,0] are concurrent — neither dominates.Worked example: detecting a concurrent write with vector clocks
Three nodes A, B, C — each vector has three slots [A, B, C]. All start at [0,0,0].
| Step | Where | Event | Vector after |
|---|---|---|---|
| 1 | A | local write x=1 — A bumps slot A | [1,0,0] |
| 2 | A to B | A sends the write to B; B receives | B merges max([0,0,0], [1,0,0]) = [1,0,0], then bumps its own slot → [1,1,0] |
| 3 | C | independently, before hearing from anyone, C does a local write x=99 | [0,0,1] |
| 4 | B to C | B sends its state to C; C receives | C merges max([0,0,1], [1,1,0]) = [1,1,1], then bumps its own slot → [1,1,2] |
Now compare the two writes. Write at A had vector [1,0,0]. Write at C had vector [0,0,1]. Is [1,0,0] ≤ [0,0,1]? Slot A says 1 ≤ 0 — no. Is [0,0,1] ≤ [1,0,0]? Slot C says 1 ≤ 0 — no. Neither dominates, so the two writes are concurrent — they happened without either knowing about the other. The system now has two valid versions of x and must either pick one (LWW), merge them, or hand both to the application as siblings.
Contrast with step 4's vector [1,1,2]: it dominates both [1,0,0] and [0,0,1] (every slot is ≥, at least one strictly greater), so the step-4 state causally happens-after both writes and has seen them both.
Logical clocks have a downside: their stamps mean nothing in wall-clock terms. Hybrid logical clocks combine a physical-time prefix with a logical counter suffix. Stamps almost always equal wall-clock time, but they never violate causality when clocks skew. CockroachDB uses HLCs for commit timestamps; YugabyteDB and MongoDB follow the same pattern.
The most aggressive solution is to bound clock uncertainty in hardware. Google's Spanner does this. Every data centre runs GPS receivers and atomic clocks; the TT.now() API returns an interval [earliest, latest] rather than a point. Spanner commits a transaction at time s = latest, then waits until earliest > s before releasing locks. The commit pays roughly 2ε of artificial latency — a handful of milliseconds in steady state — for true linearizability across continents. Tighter clocks make commits faster, which is why Google buys the GPS and atomic-clock hardware.
Consistency models
When one piece of data lives on more than one node, "what is the current value" stops being a one-line question. A consistency model is the contract about what reads are allowed to see relative to writes. Stronger models are easier to program against and more expensive to provide. Weaker models survive partitions and run faster, and your code has to work around what it can observe.
Linearizable is the strictest. Every operation appears to take effect at one instant between its invocation and its response, and the order of those instants matches real time. If write W completes before read R begins on a different client, R sees W or something later. Spanner achieves this across continents by physically bounding clock uncertainty.
Sequential drops the real-time constraint. All nodes agree on one order, but that order need not match wall clocks. A read on one node followed by a write on another might be reordered globally — but every node agrees on the same final story.
Causal drops the total order. Only causally related operations need agree on order; concurrent writes can be observed in different orders by different readers. Cheap to provide, sufficient for "I posted, then I commented on my post — others must see them in that order."
Read-your-writes is a session-level guarantee. The same client sees the effect of writes it just made. Other clients may still see stale state. Usually implemented by pinning a session to a leader or to replicas that have caught up past a known version.
Eventual is the floor. Stop writing, and replicas will converge. No bound on when. No constraint on what you observe in the meantime.
Pitfall — eventual is much weaker than it sounds. Two writes by the same user, seconds apart, can land on different replicas. A third client reading from a third replica may see the second write before the first, may see neither, or may see the first overwritten by the second after a delay. S3 shipped with eventual consistency for overwrites and deletes, and for a long time a fresh PUT followed immediately by a GET could legitimately return 404 in us-east-1. Strong read-after-write across all operations only landed in December 2020.
CAP and PACELC
The CAP theorem — conjectured by Brewer, formally proved by Gilbert and Lynch — states that during a network partition, a distributed system can guarantee at most one of:
- Consistency — linearizable reads
- Availability — every non-failing node responds successfully
Partition tolerance is non-negotiable in any system that spans more than one machine — partitions will happen — so the real choice is "during a partition, do I sacrifice C or A?".
CAP only describes the partition case. PACELC adds the steady state: if a Partition occurs, pick Availability or Consistency; Else (no partition), pick Latency or Consistency. Real systems live on both axes. Spanner is PC/EC — strict consistency always, extra latency in the steady state for commit-wait. Cassandra is PA/EL — favours availability and low latency, with consistency as a tunable knob.
Pitfall — "we picked CP" doesn't make latency vanish. Choosing consistency over availability buys correctness during partitions; it does not free you from synchronous replication, the leader bottleneck, or the failover gap when a leader dies. PACELC is the reminder that the cost is paid in the no-partition case too.
Consensus
Consensus is the canonical hard problem: a set of nodes must agree on a value (or a sequence of values), even though some may crash, messages may delay or drop, and the network may partition. The FLP impossibility result — Fischer, Lynch, and Paterson — proved that no deterministic algorithm can guarantee consensus in a fully asynchronous network with even one faulty process. Every practical algorithm gets around FLP by adding something: timeouts, randomness, or a partial-synchrony assumption.
Paxos was the first widely-deployed solution. It is correct and notoriously hard to implement. Raft, by Ongaro and Ousterhout, was designed for understandability. It is Paxos-equivalent in safety but splits the problem into three pieces with clean state machines: leader election, log replication, and safety.
Leader election
A Raft cluster of N nodes has at most one leader at a time. Time is divided into terms — monotonically increasing integers. Each node is in one of three states:
- Follower — passively receives heartbeats from a leader.
- Candidate — requested votes, waiting for a majority.
- Leader — handles all client writes for the current term.
When a follower's election timer expires (no heartbeat from a current leader), it increments its term, votes for itself, and asks the other nodes to vote. Each node votes for at most one candidate per term, and only for candidates whose log is at least as up-to-date as its own. A candidate that collects ⌈(N+1)/2⌉ votes becomes leader. Ties — multiple followers becoming candidates at once — are broken by randomized timeouts that reroll on retry, so eventually one candidate wins.
Worked example: Raft leader election when the leader crashes
Three nodes — A (leader), B, C. Current term is 5. A has been sending heartbeats every 50 ms; B and C each have an election timer set to a random value in [150ms, 300ms] that resets on every heartbeat.
| Step | What happens | State after |
|---|---|---|
| 1 | A crashes (or its network link drops). Heartbeats stop arriving at B and C. | A: down. B, C: still followers in term 5, election timers counting down. |
| 2 | B's election timer fires first (it happened to draw 180 ms; C drew 240 ms). B increments its term to 6, votes for itself, and sends RequestVote(term=6, candidateId=B) to C. | B: candidate, term 6, votes = {B}. |
| 3 | C receives the RequestVote. Its current term is 5, so 6 is newer — it updates to term 6, resets its election timer, and checks: have I voted in term 6 yet? No. Is B's log at least as up-to-date as mine? Yes. So C replies VoteGranted=true. | C: follower, term 6, votedFor = B. |
| 4 | B has 2 votes out of 3 — that's a majority (⌈4/2⌉ = 2). B becomes leader for term 6 and immediately starts sending heartbeats. | B: leader, term 6. C: follower receiving heartbeats. |
| 5 | A recovers and rejoins. It still thinks it's leader for term 5 and sends an AppendEntries(term=5) to B. B replies with its current term, 6. A sees the higher term, steps down to follower, and updates to term 6. | A: follower, term 6. |
Two invariants kept the system safe through all of this. At most one leader per term, because winning needs a majority and two majorities of three nodes must overlap on at least one node — who would have to vote twice in the same term, which the rules forbid. A higher term always wins, so a stale leader returning from the dead cannot do damage; it sees a newer term in any reply and steps down immediately.
Log replication
Once elected, the leader is the sole entry point for writes. Every change is appended to the leader's log and replicated to followers via AppendEntries RPCs. An entry is committed once a majority of nodes (including the leader) have written it durably. Only committed entries are applied to the state machine.
The majority requirement is what makes Raft tolerant of (N-1)/2 failures: with N=3 you survive 1 failure; with N=5 you survive 2. Adding nodes increases fault tolerance but also widens the quorum every write must wait for. Production deployments are almost always 3, 5, or 7. Election timeouts are typically randomized in the 150–300 ms range, so failover after a leader crash usually completes in a few hundred milliseconds.
Pitfall — quorum is a property of the cluster, not of any one operation. A 5-node cluster split 3–2 keeps the 3-node side serving; the 2-node side stalls. Add a 6th node and a 3–3 split halts both halves. Even-sized clusters are strictly worse than the odd-sized cluster one node smaller.
Raft is the algorithm behind etcd (Kubernetes' coordination layer), Consul, CockroachDB, and TiKV. ZooKeeper uses the older Zab protocol; Cassandra uses Paxos for lightweight transactions; Spanner uses a Paxos variant per shard. Different names, same shape: replicate a log, commit on majority, elect a leader when the current one falls silent.
Atomic commit and sagas
Consensus agrees on one value. Atomic commit is the related problem of agreeing whether a multi-participant transaction succeeds or aborts as a whole. The classical solution is two-phase commit (2PC).
The 2PC failure mode is brutal: between the prepare-ack and the commit broadcast, every participant is prepared — durable on disk, locks held — and waiting on a decision. If the coordinator dies in that window, participants cannot abort (someone else may have committed) and cannot commit (the decision is unknown). They wait, indefinitely, until manual recovery. Production systems fix this by replicating the coordinator's decision through consensus (Paxos-Commit, Spanner's transaction record, CockroachDB's txn record).
The alternative is to give up atomic commit entirely. A saga decomposes a long transaction into a sequence of local steps, each with a compensating action that semantically undoes it.
A saga has visible intermediate state: between T2 and T3, the customer is charged and the seat reserved but no email has been sent. Other readers will observe that. The hard engineering is in the compensations: a refund is not literally the inverse of a charge (the customer noticed, fees were paid), and not every operation has a clean undo. Sagas suit workflows where eventual consistency and visible intermediate states are acceptable; they do not replace ACID transactions inside a single database.
Replication
A single node is a single point of failure. Replication keeps copies of the data on multiple nodes, ideally in different failure domains. The trade-off is how synchronously the copies stay in sync. Three topologies cover the design space.
Leader-follower is the default of every classical relational database. Postgres, MySQL, MongoDB, Redis. One node is authoritative; followers stream the write-ahead log and apply it. Reads can serve from followers, but with replication lag — typically tens of milliseconds, sometimes seconds when a follower falls behind. Synchronous replication waits for at least one follower to ack before responding (extra round trip, bounded data loss on failover). Asynchronous replication drops the wait (faster, can lose the last few milliseconds of writes if the leader dies before replicating).
Multi-leader keeps a leader in each region so cross-region writes don't pay the round-trip latency. Each region accepts local writes and propagates them asynchronously to the others. The price is conflict resolution: two leaders may accept conflicting writes for the same key. Three resolution strategies dominate:
- Last-write-wins (LWW) — pick the write with the newest timestamp. Cheap. Drops one increment if two writes race.
- Application-defined merge — let the application reconcile (Git does this for source code; Riak does it for objects).
- CRDTs (Conflict-free Replicated Data Types) — data types whose merge function is provably commutative, associative, and idempotent, so any merge order produces the same result.
Leaderless / Dynamo-style removes the leader entirely. The client (or a coordinator) sends each write to W replicas in parallel and considers it durable when W of them ack. Reads go to R replicas and reconcile by version — Dynamo and Riak track vector clocks to surface concurrent versions, Cassandra and DynamoDB collapse them with last-write-wins timestamps. The relation W + R > N guarantees that any read quorum overlaps any write quorum on at least one node — strong consistency falls out of the arithmetic. Cassandra's QUORUM level is (N/2)+1 for both; ONE is W=1 or R=1; ALL is W=N or R=N.
Worked example: why W+R > N guarantees you see the latest write
Take the simplest non-trivial case: N=3 replicas (call them N1, N2, N3), W=2, R=2. Notice W + R = 4 > N = 3.
- A client writes
x=7. The coordinator sends the write to all three replicas in parallel and waits for any 2 acks. Suppose N1 and N2 ack first; the coordinator returns success to the client. N3 is slow or briefly unreachable and still holds the old valuex=3. - A second client now reads
x. The coordinator queries any 2 of the 3 replicas. There are three possible pairs it might pick:{N1,N2},{N1,N3}, or{N2,N3}. - Look at each pair:
{N1,N2}— both have the new value, easy.{N1,N3}— N1 hasx=7, N3 hasx=3; the coordinator sees two versions, picks the newer one (by vector clock or by timestamp), returns7, and typically triggers a read repair on N3.{N2,N3}— same thing via N2.
The key observation: there is no way to pick 2 replicas without including at least one of {N1, N2}. That's the pigeonhole: a write quorum of 2 and a read quorum of 2 in a pool of 3 must overlap on at least one node — because 2 + 2 = 4 and there are only 3 nodes to choose from, two of the chosen nodes must be the same. As long as the system can detect "this version is newer than that version", a single overlap is all you need.
The arithmetic generalizes. With N=5, W=3, R=3, every read of 3 nodes overlaps every write of 3 nodes by at least one node (3 + 3 = 6 > 5). Setting W=N, R=1 makes writes expensive and reads cheap; W=1, R=N flips that. Cassandra exposes the choice per query so you can pick the trade-off live.
Pitfall — replication lag is observable. A user posts a comment, the write goes to the leader, and the follow-up read goes to a follower that has not yet caught up. The user sees a missing post they just made. The fix is read-your-writes: route a user's reads back to the leader for a short window after a write, or use a session token that pins them to a sufficiently-fresh replica.
In the leaderless model, replicas diverge in normal operation, so the system needs explicit convergence mechanisms:
- Read repair — when a coordinator gathers a read quorum and sees disagreement, it writes the newest value back to the stale replicas on the hot path.
- Anti-entropy — a periodic background process compares replicas pairwise using Merkle trees and copies any missing entries.
- Hinted handoff — when a target replica is down at write time, the coordinator stores the write as a hint and replays it when the replica returns.
Cassandra and Riak run all three. Together they bound the staleness window to seconds or minutes.
Membership and gossip
Replicas need to know about each other — joiners, leavers, suspected-dead, configuration changes — without funneling everything through one registry. Gossip is the standard answer: each node periodically picks a few random peers and exchanges state digests. After roughly log N rounds, every node knows everything.
The same gossip channel typically carries the cluster's failure detector. A practical detector is phi accrual: instead of a hard timeout, each node continuously computes a suspicion level phi for every peer, scaled by the observed variance of past heartbeats. A noisy link raises the threshold before declaring a peer dead; a normally-quiet peer is suspected after a smaller absolute delay. Cassandra and Akka use this directly.
Geo-replication
Cross-region RTTs dwarf in-region cost, so where the leader lives and how reads route now interact with the topology choice.
Spanner and CockroachDB pay for active-active correctness with synchronous consensus per write. Cassandra and DynamoDB Global Tables pay for active-active speed with LWW and the silent data loss it implies. There is no free correct active-active.
Partitioning
Replication makes copies. Partitioning (also called sharding) splits the data so different nodes own different parts of it. The two are orthogonal: production systems replicate each partition for durability and spread partitions across nodes for capacity. The choice of how to partition decides what queries are cheap and what queries are catastrophic.
Hash-based runs the key through a hash function and assigns it to a shard. Even distribution is automatic: hash("user-42") % N doesn't care how many user-42s exist. The cost is range queries. WHERE id BETWEEN 1000 AND 2000 becomes a scatter–gather across every shard, because adjacent keys hash to arbitrary destinations.
Range-based keeps adjacent keys together; range queries hit a single shard. The cost is hotspots: one popular user's data lives on one shard, which gets 100× the traffic of the others. HBase, BigTable, and Spanner are range-partitioned; Cassandra, DynamoDB, and Riak are hash-partitioned.
Directory-based maintains an explicit lookup table from key (or key range) to shard. Maximum flexibility — arbitrary remapping — but the directory itself becomes critical-path infrastructure. The HDFS NameNode is famously this: it knew which DataNode held each block, and for years it was a single point of failure.
The hard part is not picking a strategy. It is changing your mind later. Naive hash % N rehashes every key when N changes, which is unacceptable in production. Consistent hashing solves this. Hash both keys and nodes onto the same ring; each key is owned by the first node clockwise from its hash position.
When a node joins or leaves, only the keys in its arc need to move — roughly 1/N of the data. Virtual nodes (each physical node placed at many ring positions) smooth out the load. Without them, removing a node dumps all its load onto the single neighbour that owns the next-clockwise arc.
Resharding takes time. A 1 TB shard at 10 Gbps takes about 13 minutes to copy at line rate. Production rebalances throttle to leave headroom for live traffic, stretching this to hours. During the move, both source and destination must serve reads consistently — typically via a "source authoritative until cutover" pattern with a brief read-only window. This is the engineering work behind every "we doubled our shard count last weekend" blog post.
Pitfall — celebrity hot keys. Hash partitioning evenly distributes keys but not load. One Twitter celebrity, one hot product on Black Friday, one trending hashtag — each concentrates millions of operations on one partition no matter how good the hash. The mitigations are awkward: replicate the hot key to extra nodes (read fan-out), add a per-key suffix to split the load (celebrity#0 … celebrity#15) at the cost of fan-in on read, or kick the hot key out of the cluster into an in-memory cache. There is no clean answer; popular things are physically harder to serve than unpopular things.
Standards
Distributed systems has a smaller standards-body footprint than the rest of this stack — most of the canon is research papers, not RFCs.
- Lamport, "Time, Clocks, and the Ordering of Events in a Distributed System" — Communications of the ACM, 21(7), 1978. The foundational paper. Defines logical clocks and the happens-before relation
→that every later distributed-systems argument is built on. - Lamport, "The Part-Time Parliament" — ACM TOCS, 16(2), 1998 (written 1989, famously rejected in its first submission). The original Paxos paper. Read after the simpler one, not before.
- Lamport, "Paxos Made Simple" — ACM SIGACT News, 32(4), 2001. The readable version. Start here for Paxos.
- Ongaro & Ousterhout, "In Search of an Understandable Consensus Algorithm (Raft)" — USENIX ATC 2014. The paper. The accompanying website (raft.github.io) has the full TLA+ spec and reference implementations.
- Fischer, Lynch, Paterson, "Impossibility of Distributed Consensus with One Faulty Process" — JACM, 32(2), 1985. Why every consensus algorithm needs an extra assumption (timeouts, randomness, or partial synchrony).
- Brewer, CAP conjecture — PODC 2000 keynote (no formal paper). Gilbert & Lynch, "Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-tolerant Web Services" — ACM SIGACT News, 33(2), 2002. The proof.
- Abadi, "Consistency Tradeoffs in Modern Distributed Database System Design: CAP is Only Part of the Story" — IEEE Computer, 45(2), 2012. The PACELC refinement.
- DeCandia et al., "Dynamo: Amazon's Highly Available Key-value Store" — SOSP 2007. The leaderless quorum model that named a generation of systems (Cassandra, Riak, Voldemort).
- Corbett et al., "Spanner: Google's Globally-Distributed Database" — OSDI 2012. Linearizability across continents using TrueTime; the existence proof that CP-with-low-latency is buildable if you put atomic clocks and GPS receivers in your data centres.
- Herlihy & Wing, "Linearizability: A Correctness Condition for Concurrent Objects" — ACM TOPLAS, 12(3), 1990. The formal definition of linearizable.
- Karger et al., "Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web" — STOC 1997. The ring construction used by every Dynamo-style system since.
- Shapiro, Preguiça, Baquero, Zawirski, "Conflict-free Replicated Data Types" — SSS 2011 (and the longer INRIA tech report RR-7687). The formal foundation for CRDTs — counters, sets, sequences, registers that merge deterministically.
- Chandra & Toueg, "Unreliable Failure Detectors for Reliable Distributed Systems" — JACM, 43(2), 1996. Formalizes what "I think peer X is dead" can possibly mean on an asynchronous network.
- TLA+ — Lamport's specification language for concurrent and distributed systems (lamport.azurewebsites.net/tla/tla.html). Used in production at Amazon (DynamoDB, S3) and Microsoft (Azure Cosmos DB) to verify protocol correctness before implementation.
- Vector clocks — Fidge (1988) and Mattern (1989), independent. The natural generalization of Lamport timestamps to detect concurrent events.
- gRPC / Protocol Buffers — handed forward from Act I (serialization) and Va (the wire). The transport every modern distributed control plane defaults to.
- Forward refs — Act Vc picks up cloud-native deployment patterns (containers, schedulers, service meshes, the operational layer that sits on top of the primitives in this chapter). Act VI covers database internals — B-trees, LSMs, query optimization, transaction protocols — that build on consensus, replication, and partitioning as primitives.
Branches that earn their own article.
- Paxos (the original consensus algorithm).
- Byzantine fault tolerance (BFT).
- Logical clocks (Lamport, vector, hybrid logical).
- CRDTs (conflict-free replicated data types).
- Distributed transactions (2PC, 3PC, Saga pattern).
- Gossip protocols.
- PACELC theorem.
- Formal verification of distributed protocols (TLA+).