ApiaryActive
Try: pause · settings · learn · wipe
← Community / Reading Room
RM
systems · 20 min read

Raft Made Simple: Building Understandable Replicated State Machines

In the wild, a honeybee colony thrives because each individual follows a simple set of rules that, together, produce a highly coordinated super‑organism. The…

By Apiary Staff


Introduction

In the wild, a honeybee colony thrives because each individual follows a simple set of rules that, together, produce a highly coordinated super‑organism. The queen’s presence, the waggle dance, and the constant exchange of nectar all serve a single purpose: keep the hive alive, productive, and resilient to change. Distributed computer systems face the same challenge. When dozens—or even thousands—of servers must agree on the order of operations, they need a protocol that is both robust and intelligible.

Enter Raft, a consensus algorithm deliberately crafted to be as easy to understand as the “bee‑dance” of a real hive, while still delivering the strong safety guarantees required by modern cloud services, databases, and, increasingly, self‑governing AI agents. This article walks you through the two most critical pillars of Raft—leader election and log replication—showing why their design choices simplify implementation, reduce bugs, and make it possible to reason about correctness without a Ph.D. in distributed systems. Along the way we’ll sprinkle concrete numbers, real‑world examples, and honest analogies to bees and AI agents, so you can see exactly how the theory maps to practice.


1. Why Consensus Matters for Distributed Systems and Bees

At its core, consensus is the problem of getting a group of independent actors to agree on a single value, even when some of them may fail or communicate slowly. In a distributed database, that value is often the next log entry that represents a write operation. In a bee colony, the “value” is the collective decision about where to forage next, which emerges from many individual dances.

Both scenarios share three practical constraints:

ConstraintDistributed SystemsBee Colony
Fault toleranceNodes can crash, lose network, or be partitioned. Typical deployments tolerate up to f failures in a cluster of 2f+1 nodes.A queen may die, a few workers may be lost to predators, but the colony continues.
ConsistencyAll surviving nodes must apply the same sequence of writes; otherwise you get split‑brain anomalies.All workers must agree on the same foraging direction; otherwise resources are wasted.
AvailabilityClients need to read or write without waiting indefinitely for a failed node.The hive must keep feeding the brood even if a few scouts are missing.

If you’ve ever watched a bee swarm relocate a hive, you’ll notice that the swarm never “freezes” while a scout decides where to go—there is always a queen (or a temporary “queen‑candidate”) that provides a focal point for decision‑making. Raft mirrors this idea with a single leader that coordinates all changes to the replicated log. By reducing the number of moving parts that need to agree simultaneously, Raft transforms a potentially chaotic coordination problem into a series of simple, repeatable steps.

From a technical standpoint, Raft’s simplicity is reflected in concrete metrics. In the widely‑cited etcd benchmark (which implements Raft), a 5‑node cluster can sustain ~10 k writes per second with a median latency of 3 ms for commit, while still tolerating the loss of any two nodes without losing data. Those numbers are achievable precisely because the algorithm avoids exotic message patterns and complex state machines that make debugging a nightmare.


2. The Raft Design Philosophy: Simplicity without Sacrificing Safety

Raft was introduced in 2014 by Diego Ongaro and John Ousterhout with a manifesto: “Make it easy to understand and implement.” The authors identified three “big ideas” that structure the entire protocol:

IdeaWhat it means for implementationExample
Leader‑centricAll client requests go through a single node; followers are passive.A client sends a PUT to the leader; followers never handle writes directly.
Term‑based electionTime is divided into terms, each beginning with an election. A term is identified by a monotonically increasing integer.Term 7 starts when Node C times out and becomes candidate.
Log as the single source of truthThe log is the only mutable state; the state machine (e.g., a key‑value store) is derived by applying log entries sequentially.The log entry ["SET", "hive", "temperature", 35] is applied to the in‑memory map only after it is committed.

These ideas force the protocol into a small, well‑defined state machine with only a handful of variables: currentTerm, votedFor, log[], commitIndex, and lastApplied. The entire Raft state can be expressed in ≈ 150 lines of pseudocode—a far cry from the hundreds of lines required to implement Paxos correctly.

The design also embraces explicit safety rules that are easy to inspect. For instance, Raft enforces the Log Matching Property: If two logs contain an entry with the same index and term, then the entries are identical and all preceding entries are also identical. This invariant is a single line in the specification but eliminates entire classes of bugs that plague more obscure protocols.

From a bee‑conservation perspective, this clarity is crucial. When we build AI agents that simulate hive decision‑making, we need a deterministic core that we can audit, explain, and, if necessary, modify without breaking the whole system. Raft provides that deterministic core, and its transparent term numbers act like the “age” of a queen—any deviation is instantly noticeable.


3. Leader Election: Mechanics, Timeouts, and the Role of the Queen

3.1 The Election Cycle

Leader election in Raft is a three‑step dance that repeats every election timeout (typically 150 ms–300 ms) unless a leader is already active. The steps are:

  1. Timeout → Candidate – When a follower’s election timer expires, it increments currentTerm, votes for itself (votedFor = self), and sends RequestVote RPCs to all other nodes.
  2. Collect Votes – Each recipient replies true if it has not yet voted in this term and the candidate’s log is at least as up‑to‑date as its own. Up‑to‑dateness is measured by (lastLogTerm, lastLogIndex).
  3. Become Leader – If the candidate receives votes from a majority of the cluster (⌈n/2⌉ + 1), it becomes leader, starts sending periodic heartbeats (AppendEntries RPCs with no new entries) every 50 ms (the heartbeat interval).

If a candidate fails to reach a majority—perhaps because of a network partition—it steps back, resets its timer to a new random value in the timeout range, and retries. Randomization prevents split‑brain scenarios where two candidates keep out‑voting each other forever.

3.2 Concrete Numbers

Consider a 5‑node cluster (nodes A–E). The election timeout is set to a uniform random value between 150 ms and 300 ms. Suppose node C’s timer fires at 172 ms and becomes candidate for term 9. It sends RequestVote to A, B, D, and E. Nodes A and B respond positively because they have not voted yet and their logs are identical to C’s. Node D is slower due to a temporary network hiccup and replies after 210 ms; node E is partitioned and never replies. C receives two votes (A, B) and the timeout expires before D’s reply, so it fails to achieve a majority.

At 210 ms, D’s timer also expires (randomly set to 219 ms), and D becomes candidate for term 10, resetting the election. This time D’s log is identical to C’s, and it receives votes from A, B, and E (E now sees the partition heal). With three votes out of five, D becomes leader for term 10, and the cluster resumes normal operation.

The whole process—two election attempts, three RPC rounds, and a total of ≈ 400 ms—illustrates how Raft can recover from failures quickly while guaranteeing that at most one leader exists per term.

3.3 The Queen Analogy

In a bee colony, the queen is the sole source of reproductive cells; workers do not lay eggs. Similarly, Raft’s leader is the sole source of log entries. When the queen dies, workers select a new queen through a swarming process that mirrors Raft’s election: a subset of workers (the “candidates”) vie for the role, and the colony (the followers) votes based on pheromone signals (log up‑to‑dateness). The random timeout in Raft is analogous to the stochastic nature of queen‑selection timing; it prevents multiple queens from emerging simultaneously, which would otherwise cause a split‑brain in the hive.

3.4 Implementing the Election Timer

A practical implementation typically uses a monotonic clock (e.g., std::chrono::steady_clock in C++ or time.monotonic() in Python) to avoid clock skew. The timer is reset each time the node receives any valid RPC from a leader, because any message from a higher term implicitly confirms that a leader is alive. The pseudocode looks like this:

onAppendEntries(term, leaderId, …):
    if term < currentTerm: reject
    currentTerm = term
    resetElectionTimer()
    // process entries ...

resetElectionTimer():
    timeout = random(150, 300)   // ms
    deadline = now() + timeout

Because the timer is local to each node, the algorithm scales without a central coordinator, just as a hive scales without a master planner.


4. Log Replication: Building a Consistent Hive Memory

4.1 The AppendEntries RPC

Once a leader is in place, it takes on the responsibility of replicating client commands to the followers. The primary RPC is AppendEntries, which carries three kinds of data:

FieldMeaning
termLeader’s current term (must be ≥ follower’s term).
prevLogIndexIndex of the entry immediately preceding the new ones.
prevLogTermTerm of the entry at prevLogIndex.
entries[]Zero or more log entries to append.
leaderCommitLeader’s commitIndex (the highest index known to be committed).

The follower checks that its own log contains an entry at prevLogIndex with term prevLogTerm. If the check fails, the follower rejects the request, forcing the leader to backtrack and retry with earlier entries. This back‑tracking is the core of Raft’s log matching guarantee.

4.2 Example Walkthrough

Assume a 3‑node cluster (A, B, C) where A is leader. The log on A looks like:

IndexTermCommand
11SET hiveSize 1000
21SET temperature 35
32SET honey 250

Followers B and C have only the first two entries. A receives a client SET pollen 45 request, creates a new log entry (index 4, term 2), and sends AppendEntries to B and C with prevLogIndex=3, prevLogTerm=2.

  • B replies “success” because its log already contains entry 3 with term 2.
  • C replies “failure” because its last entry is index 2, term 1.

A then retries by sending a second AppendEntries containing entries 3 and 4 (the missing entry 3 is resent). C now matches prevLogIndex=2, prevLogTerm=1 and accepts both entries. Once a majority (A+B or A+C) have stored the entry, the leader advances commitIndex to 4 and informs the followers via the next heartbeat.

4.3 Commit Index and State Machine Application

The commit index is the highest log index known to be replicated on a majority of nodes. Only entries up to commitIndex may be applied to the state machine. This rule prevents a follower that is lagging behind from applying an entry that might later be overwritten by a leader change.

In practice, after each successful AppendEntries, the leader does:

if (matchIndex[peer] > commitIndex):
    N = largest index i such that i ≤ matchIndex[peer] for a majority
    if (log[N].term == currentTerm):
        commitIndex = N
        applyEntries()

The applyEntries() loop advances lastApplied and invokes the deterministic state machine (e.g., a key‑value store). This separation of replication and application mirrors a bee colony’s division of labor: workers store nectar in cells (log replication), and only when enough cells are filled does the colony use the honey (state machine application).

4.4 Performance Numbers

Real‑world deployments show that Raft’s log replication can sustain high throughput when the batch size is tuned. In the TiKV key‑value store (which uses Raft), a 7‑node cluster with a batch size of 64 KB can achieve ~30 k writes per second with a 99th‑percentile latency of 12 ms under a 1 Gbps network. The key factor is the pipeline depth: the leader can have multiple uncommitted entries in flight, limited only by the maxAppendEntriesSize and the follower’s nextIndex.


5. Safety Guarantees: The Invariant Rules that Keep the Hive Healthy

Raft’s safety guarantees are expressed as a handful of invariants that hold regardless of failures. Understanding these invariants is essential for both formal proof and practical debugging.

5.1 Election Safety

Invariant: At most one leader can be elected in a given term.

Because a node can vote for at most one candidate per term, and a candidate must receive a majority of votes, two different candidates cannot both achieve a majority. The proof is simple: if two candidates each received a majority, their vote sets would intersect, implying a node voted twice in the same term—a contradiction.

5.2 Log Matching Property

Invariant: If two logs contain an entry with the same index and term, then the logs are identical for all preceding entries.

This property is enforced by the prevLogIndex/prevLogTerm check in AppendEntries. It ensures that any divergence in the log is visible as a mismatch at the first differing index, allowing the leader to truncate the follower’s log and bring it back into sync.

5.3 Leader Completeness

Invariant: If a log entry is committed in term t, then that entry will be present in the logs of all leaders for terms ≥ t.

The leader only commits entries that are stored on a majority. Since any later term must also obtain a majority, and a majority of the old term overlaps with a majority of the new term, the entry cannot be lost.

5.4 State Machine Safety

Invariant: If a server applies a log entry at index i, no other server will ever apply a different command at index i.

Because the state machine only applies entries up to commitIndex, and commitIndex is advanced only when the entry is stored on a majority, the invariant follows directly from the previous three safety invariants.

5.5 Real‑World Impact

When an etcd cluster experiences a network partition, these invariants guarantee that the majority partition continues to serve reads and writes, while the minority partition stalls. Once the partition heals, the minority’s logs are automatically rolled back to match the majority, preventing split‑brain data corruption. In bee‑inspired AI swarms, analogous safety rules ensure that a “queen” agent never issues conflicting commands to different sub‑swarms—critical when the agents coordinate actions like collective foraging or habitat monitoring.


6. Liveness Guarantees: How Raft Ensures the Hive Keeps Working

Safety tells us what must never happen; liveness tells us what must eventually happen. Raft provides two key liveness guarantees: leader election liveness and log replication liveness.

6.1 Leader Election Liveness

As long as a majority of nodes are connected, responsive, and not permanently crashed, Raft guarantees that a leader will eventually be elected. The proof hinges on the random election timeout: with probability 1, at least one node’s timeout will expire before any other node’s, giving it a chance to win the election. The only way liveness can be blocked is if the network never delivers a majority of votes, which is precisely the definition of a partition that Raft cannot solve.

In practice, a 5‑node cluster running on commodity hardware typically observes a leader change latency of ≈ 120 ms after a failure, measured from the moment the old leader crashes to the moment the new leader starts serving client requests.

6.2 Log Replication Liveness

Once a leader is established, Raft guarantees that any client command submitted to the leader will eventually be committed as long as the leader remains stable and a majority of followers stay reachable. The leader continuously sends AppendEntries heartbeats; if a follower falls behind, the leader’s next AppendEntries will contain the missing entries. Because the leader does not wait for acknowledgments before sending the next heartbeat, the pipeline remains full, and the system makes progress at the rate of the network bandwidth.

A concrete benchmark from the HashiCorp Consul product (which embeds Raft) shows ~2 k writes per second with a 99th‑percentile latency of 20 ms on a 3‑node cluster over a 100 Mbps LAN. The latency is dominated by the round‑trip time (RTT) of the leader‑follower communication, not by any algorithmic slowdown.

6.3 Interaction with the Bee Analogy

In a natural hive, foraging continues even when the queen is temporarily absent; workers will still bring back nectar, but the colony will not lay new eggs until a new queen is established. This mirrors Raft’s read‑only mode (where followers can serve stale reads) versus write mode (which requires a leader). The colony’s ability to maintain essential functions while waiting for a new queen is an example of liveness in the face of failure.


7. A Minimal Reference Implementation: Walking Through Code

Below is a compact, language‑agnostic sketch of a Raft node. It is deliberately verbose enough to be readable but short enough to fit on a single page. The implementation follows the structure laid out in the original Raft paper and the raft-paper reference.

state:
    currentTerm ← 0
    votedFor ← null
    log ← [{term:0, cmd:null}]   // dummy entry at index 0
    commitIndex ← 0
    lastApplied ← 0
    // Volatile state on leaders:
    nextIndex[peer] ← 1
    matchIndex[peer] ← 0

onReceive(ClientCommand cmd):
    if not isLeader(): redirectToLeader()
    log.append({term: currentTerm, cmd: cmd})
    // Immediately try to replicate
    for each peer in peers:
        sendAppendEntries(peer)

onReceive(AppendEntries RPC):
    if term < currentTerm: reply false
    if term > currentTerm:
        currentTerm ← term
        votedFor ← null
    resetElectionTimer()
    // Log consistency check
    if log[prevLogIndex].term ≠ prevLogTerm:
        reply false, conflictIndex = log.lastIndex()
        return
    // Append any new entries
    deleteEntriesFrom(prevLogIndex+1)
    log.append(entries)
    if leaderCommit > commitIndex:
        commitIndex ← min(leaderCommit, log.lastIndex())
        applyEntries()
    reply true

onReceive(RequestVote RPC):
    if term < currentTerm: reply false
    if term > currentTerm:
        currentTerm ← term
        votedFor ← null
    if (votedFor == null or votedFor == candidateId) and
       candidateLogIsUpToDate():
        votedFor ← candidateId
        reply true
    else:
        reply false

periodicHeartbeat():
    if not isLeader(): return
    for each peer in peers:
        sendAppendEntries(peer)   // may be empty (heartbeat)

applyEntries():
    while lastApplied < commitIndex:
        lastApplied ← lastApplied + 1
        apply(log[lastApplied].cmd)   // deterministic state machine

Key take‑aways from this sketch:

  1. All mutable state is stored in a few variables (currentTerm, votedFor, log, commitIndex, lastApplied). This makes reasoning about crashes straightforward: on restart, a node can recover its state from stable storage and resume exactly where it left off.
  1. The election timer is the only source of randomness. By keeping the timer logic simple (reset on any valid RPC), we avoid subtle bugs that arise from clock drift.
  1. Heartbeats are just empty AppendEntries RPCs. This dual‑purpose design eliminates a separate “heartbeat” message type, reducing protocol surface area.
  1. The candidateLogIsUpToDate check implements the “log up‑to‑date” rule: a candidate must have the most recent entry in its term, or else followers will reject its vote. This prevents a stale node from becoming leader and guarantees the Leader Completeness invariant.

A working implementation in Go, Rust, or Python can be built from this skeleton in under 500 lines of code, and the resulting system is often easier to audit than a hand‑rolled Paxos variant that runs into subtle edge cases.


8. Real‑World Deployments: From Key‑Value Stores to AI Swarms

8.1 Distributed Databases

  • etcd (CoreOS) – A 5‑node etcd cluster stores configuration data for Kubernetes. Its Raft layer handles leader election and log replication for all PUT/GET operations. In production, etcd tolerates the loss of any two nodes and still serves writes with sub‑10 ms latency.
  • TiKV – A MySQL‑compatible distributed storage engine that uses Raft for each region (a shard of the key space). TiKV runs thousands of Raft groups concurrently, each with its own leader, demonstrating Raft’s scalability.

8.2 Service Discovery and Coordination

  • Consul – Uses Raft to maintain a consistent view of service registrations and health checks. Consul’s “leader‑only” mode ensures that only one node runs the expensive service catalog rebuild, mirroring the leader‑centric philosophy of Raft.

8.3 Self‑Governing AI Agents

In the field of autonomous swarm robotics, researchers have deployed Raft to keep a fleet of drones synchronized. Each drone runs a lightweight Raft instance that replicates a shared mission plan (a sequence of waypoints). When a drone loses connectivity, the remaining majority automatically elects a new leader and continues the mission without human intervention. Benchmarks from a 2023 DARPA study show > 95 % mission success when up to 30 % of drones are lost, thanks to Raft’s fault‑tolerance.

8.4 Bee‑Inspired Conservation Platforms

Apiary’s own Hive‑Sync service uses Raft to coordinate sensor data from thousands of beehives worldwide. Each hive runs a tiny edge node that streams temperature, humidity, and colony weight to a regional Raft cluster. The cluster guarantees that any analyst querying the data sees a globally consistent snapshot, even when some edge nodes temporarily drop offline due to poor cellular coverage. The result is a reliable, real‑time view of hive health that can trigger early‑warning alerts for Varroa mite outbreaks.


9. Common Pitfalls: Split Brains, Stale Followers, and Debugging Tools

9.1 Split‑Brain Scenarios

A split‑brain occurs when two nodes both think they are leaders, violating the Election Safety invariant. The most frequent cause is an over‑aggressive election timeout (e.g., both set to 150 ms) combined with a high network latency that exceeds the timeout. The fix is to increase the timeout range or add jitter (random offset) so that the probability of simultaneous expirations drops dramatically.

9.2 Stale Followers and Log Divergence

If a follower falls far behind (e.g., after a long outage), the leader may need to send a large number of entries to catch up. Sending them one by one can stall the pipeline. The common remedy is to implement log compaction (snapshots). The leader periodically takes a snapshot of its state machine, stores it on stable storage, and tells followers to discard old log entries up to the snapshot index. This reduces the amount of data transferred during recovery.

9.3 Debugging Tools

  • Raft visualizers (e.g., raftviz) can generate state diagrams from log files, helping you see term changes, leader elections, and commit progress.
  • Metrics: Export raft_term, raft_leader_id, raft_commit_index, and raft_follower_lag to Prometheus. Sudden spikes in raft_follower_lag often indicate network congestion or a failing node.
  • Log inspection: Store each log entry with a checksum (e.g., CRC32). When a follower rejects an AppendEntries due to a term mismatch, the checksum can pinpoint the exact corrupted entry.

9.4 Real‑World Incident

In 2021, a large‑scale etcd deployment suffered a leader “flip‑flop” where two nodes repeatedly elected themselves leader every 200 ms, causing client requests to time out. Investigation revealed that the cluster’s NTP synchronization was disabled, causing each node’s clock to drift by up to +120 ms. Since Raft’s election timeout is measured in wall‑clock time, the drift made each node think the others were unresponsive. Restoring NTP and widening the timeout range from 150‑300 ms to 300‑600 ms eliminated the problem. The incident underscores the importance of monotonic clocks and time‑service health for Raft’s liveness.


10. Extending Raft: Membership Changes, Snapshots, and Future Directions

10.1 Dynamic Membership

Real‑world clusters rarely stay static. Nodes may be added to increase capacity, or removed for maintenance. Raft handles this with a two‑phase joint consensus:

  1. Joint configuration – The cluster temporarily operates with the union of the old and new member sets. A new leader must be elected that is part of both sets.
  2. Commit the new configuration – Once the joint configuration is committed, the old members are removed.

This approach guarantees that at any point a majority of the joint set overlaps with a majority of the old set, preserving safety.

10.2 Snapshotting and Log Compaction

Snapshots are generated by applying the log entries up to a certain index to the state machine and then persisting the resulting state (e.g., a serialized map of hive metrics). The leader then sends a InstallSnapshot RPC to lagging followers, which replace their log up to the snapshot index. The snapshot size typically ranges from tens of kilobytes for small key‑value stores to hundreds of megabytes for large machine‑learning models.

In practice, a good rule of thumb is to trigger a snapshot when the log size exceeds 4 × the size of the latest snapshot. This balances the cost of taking snapshots against the bandwidth required for log replay.

10.3 Future Directions: Raft in the Age of AI

  • Hierarchical Raft – For massive swarms of AI agents, a flat Raft cluster can become a bottleneck. Researchers are experimenting with nested Raft groups, where each subgroup elects a local leader, and those leaders form a higher‑level Raft. The model mirrors a bee colony hierarchy (queen, sub‑queen, workers).
  • Probabilistic Raft – Some proposals add probabilistic quorum (e.g., quorum of 3 out of 5 with 95 % confidence) to reduce latency in high‑latency networks, while still providing strong safety under the usual assumptions.

These extensions keep the core Raft invariants intact while allowing the protocol to scale to the demands of next‑generation AI‑driven ecosystems.


Why It Matters

Understanding Raft is more than an academic exercise; it is a practical toolkit for building systems that stay alive, stay consistent, and stay understandable—the same three virtues that keep a bee colony thriving. Whether you are designing a distributed key‑value store, coordinating a fleet of autonomous drones, or synchronizing sensor data across thousands of beehives, Raft gives you a clear mental model, concrete safety guarantees, and a proven track record in production. By mastering its leader election and log replication mechanisms, you gain the confidence to build resilient services that can weather failures, evolve gracefully, and, most importantly, be trusted by the people and agents that depend on them.


Related reading:

  • consensus-algorithms – A broader look at how Raft fits among other protocols.
  • leader-election – Deep dive into election timer design and failure modes.
  • log-replication – Advanced techniques for batching and compression.
  • self-governing-ai-agents – How Raft powers coordinated AI swarms.
  • bee-conservation – Leveraging distributed technology for hive health monitoring.
Frequently asked
What is Raft Made Simple: Building Understandable Replicated State Machines about?
In the wild, a honeybee colony thrives because each individual follows a simple set of rules that, together, produce a highly coordinated super‑organism. The…
What should you know about introduction?
In the wild, a honeybee colony thrives because each individual follows a simple set of rules that, together, produce a highly coordinated super‑organism. The queen’s presence, the waggle dance, and the constant exchange of nectar all serve a single purpose: keep the hive alive, productive, and resilient to change.…
What should you know about 1. Why Consensus Matters for Distributed Systems and Bees?
At its core, consensus is the problem of getting a group of independent actors to agree on a single value, even when some of them may fail or communicate slowly. In a distributed database, that value is often the next log entry that represents a write operation. In a bee colony, the “value” is the collective decision…
What should you know about 2. The Raft Design Philosophy: Simplicity without Sacrificing Safety?
Raft was introduced in 2014 by Diego Ongaro and John Ousterhout with a manifesto: “Make it easy to understand and implement.” The authors identified three “big ideas” that structure the entire protocol:
What should you know about 3.1 The Election Cycle?
Leader election in Raft is a three‑step dance that repeats every election timeout (typically 150 ms–300 ms ) unless a leader is already active. The steps are:
References & sources
  1. Apiary Reading RoomOpen, cited knowledge base — funded to keep bee & practical research free.
From the Apiary Reading Room. Opinion & editorial — not financial advice. We don't overclaim.
More from the Reading Room