Google's coordination service that underpins GFS, Bigtable, and MapReduce—and inspired ZooKeeper
Chubby is a distributed lock service that provides coarse-grained locking and reliable small-file storage for loosely-coupled distributed systems. Rather than exposing raw Paxos consensus to developers, Chubby wraps it in a familiar file-system interface with locks. It serves as the coordination backbone for Google's infrastructure—GFS uses it for master election, Bigtable for tablet server coordination, and MapReduce for task assignment. The design prioritizes availability and reliability over raw performance, using aggressive caching and sessions to handle thousands of clients.
Google chose a centralized lock service over a Paxos library because: (1) developers are more familiar with locks than consensus protocols, (2) a service reduces the number of servers needed for consensus, and (3) a lock service naturally provides a place to store small amounts of metadata.
Chubby is designed for locks held for hours or days, not milliseconds. This enables aggressive client-side caching and session-based semantics. Fine-grained locks would require too much server load and wouldn't benefit from caching.
Chubby presents a simple file system with directories and files (nodes). Each node can act as a lock and store small data. This familiar interface hides Paxos complexity while enabling both locking and configuration storage.
Clients maintain sessions with the Chubby cell via periodic KeepAlives. Sessions bundle lease extensions, cache invalidations, and event delivery. If a session expires, all locks and ephemeral files are released—critical for failure detection.
To prevent issues with delayed messages, Chubby provides sequencers—opaque tokens encoding lock identity and generation. Servers can verify a client still holds the lock by checking the sequencer, preventing race conditions.
Clients cache file data and metadata indefinitely, with write-through cache coherence. This transforms Chubby from a bottleneck into a highly scalable read-heavy system—critical since most clients only need to read configuration data.
By the mid-2000s, Google's infrastructure consisted of thousands of machines running distributed systems like GFS, Bigtable, and MapReduce. These systems faced common coordination challenges:
Leader Election: GFS needs a single master at any time. What happens when the master crashes? How do we elect a new one without split-brain?
Configuration Management: Bigtable tablet servers need to know the current master's address. How do we distribute this reliably?
Service Discovery: MapReduce workers need to find the job coordinator. How do we maintain an up-to-date registry?
Distributed Locking: Multiple processes need exclusive access to resources. How do we implement locks that survive failures?
The naive solution—each team implements Paxos—had problems:
Key insight: A lock service is a better abstraction than a consensus library for most developers.
A Chubby deployment (called a cell) consists of five servers (replicas) that use Paxos to elect a master. Clients communicate only with the master; replicas serve only to provide fault tolerance.
Cell structure:
Client connection flow:
Master election:
Chubby presents a simple Unix-like file system interface. This design choice was deliberate—file systems are familiar to developers and naturally support both locking (files as locks) and data storage (files as configuration).
Namespace structure:
Node types:
| Type | Persistence | Use Case | |------|-------------|----------| | Permanent | Deleted explicitly | Configuration, master address | | Ephemeral | Deleted when session ends | Service registration, presence |
Node metadata (like Unix inodes): - Instance number: Increases each time node is created - Content generation: Increases on each write - Lock generation: Increases when lock is acquired - ACL generation: Increases when ACLs change - Checksum: 64-bit content hash
API operations:
Open(path, mode) → Handle
Close(handle)
GetContentsAndStat(handle) → (data, metadata)
SetContents(handle, data)
Delete(path)
Acquire(handle) → Lock
Release(handle)Key constraints: - Files are small (256KB limit, typically <1KB) - No append, move, or link operations - No partial reads/writes—always entire file - Directories contain only files/directories, no data
Every Chubby node (file or directory) can act as a reader-writer lock. Unlike database locks, Chubby locks are advisory—they don't prevent access to the underlying data, only coordinate willing participants.
The distributed lock problem:
Distributed locks have a fundamental issue: message delays can cause incorrect behavior even with perfect locking.
Sequencers: The solution
Chubby provides sequencers—opaque byte-strings that describe the lock state: - Lock name - Lock mode (exclusive/shared) - Lock generation number
Safe locking pattern:
C1: Acquire() → sequencer{gen=5}
C1: Request(sequencer{gen=5}) → Server
Server: Check sequencer → gen=5 current? Yes → Process
[Lock expires, C2 acquires]
C2: Acquire() → sequencer{gen=6}
C1: Request(sequencer{gen=5}) → Server [delayed]
Server: Check sequencer → gen=5 current? No (current=6) → RejectLock-delay: For servers that can't check sequencers, Chubby implements a lock-delay period (default 1 minute). After lock release, Chubby prevents other clients from acquiring the lock, allowing in-flight requests to complete.
A session is the relationship between a Chubby client and a Chubby cell. Sessions have leases—time periods during which the master guarantees not to terminate the session unilaterally. This enables client-side caching.
KeepAlive protocol:
Why block KeepAlives?
Jeopardy period:
When a client loses contact with Chubby:
Client failure detection:
Chubby clients maintain a write-through cache of file data and metadata. This is critical for scalability—without caching, Chubby would be a bottleneck for thousands of clients reading configuration.
Caching rules:
What's cached:
| Data | Cached? | Notes | |------|---------|-------| | File contents | Yes | Most important for config files | | File metadata | Yes | Avoids stat() calls | | Directory contents | Yes | For namespace traversal | | Lock state | No | Must check master for safety | | Open handles | Yes (locally) | Recreated on failover |
Why strict consistency works here:
Caching enables scale:
Without caching, if 10,000 clients read config every second: - 10,000 RPCs/second to Chubby master - Master becomes bottleneck
With caching: - Initial read: 10,000 RPCs (one-time) - Subsequent reads: 0 RPCs (served from cache) - Config changes: ~10,000 invalidations (rare event)
Chubby clients can subscribe to events on files and directories. Events enable reactive programming—clients respond to changes rather than polling.
Event delivery:
Common patterns:
Master election with events:
1. Candidates try to acquire exclusive lock on /service/master
2. Winner writes its address to the file
3. All clients watch /service/master for content changes
4. On change event, clients read new master address
5. If master crashes, lock released, new candidate wins
6. Clients notified via event, read new addressService discovery with events:
1. Service instances create ephemeral children in /service/instances/
2. Each file contains instance address
3. Clients watch directory for child changes
4. Instance crashes → session expires → ephemeral file deleted
5. Clients notified via event, update instance listEvent ordering guarantees:
The most common Chubby use case is master election—ensuring exactly one server acts as master for a service. This pattern underpins GFS, Bigtable, and many other Google systems.
Master election steps:
Why this works:
Important detail: The lock file is permanent
The master file is not ephemeral—it persists across master changes. Only the lock is tied to the session. This ensures: - Master address survives master restart - Clients can read address even during transition - New master writes new address (doesn't recreate file)
When the Chubby master fails, a new master must be elected from the replicas. This process is carefully designed to maintain all session guarantees.
Failover procedure:
Epoch numbers:
Each master has an epoch number (increments on failover). This prevents old masters from affecting the system:
Handle reconstruction:
After failover, clients may need to rebuild their handles:
Failover time: Typically a few seconds. Dominated by: - Detecting old master failure (lease timeout) - Paxos election (usually fast) - New master startup (reading log)
The Chubby master stores all data in a local database, replicated via Paxos to ensure durability and consistency across the cell.
Storage layer:
What's stored in Paxos log:
Consistency model:
Backup and recovery:
A single Chubby cell serves an entire datacenter—potentially tens of thousands of clients. Several mechanisms enable this scale without making Chubby a bottleneck.
Scaling mechanism 1: Client caching
Most Chubby data (configuration files) is read-heavy: - Clients cache indefinitely - Only cache invalidations hit the server - Typical ratio: 10,000 reads per write - Master handles writes only
Scaling mechanism 2: KeepAlive coalescing
Each client has exactly one outstanding KeepAlive: - Not periodic polling (wasteful) - Master blocks response until needed - Piggybacks: lease extension, events, invalidations - One RPC serves multiple purposes
Scaling mechanism 3: Proxies
For very large cells, Chubby supports proxy servers: - Trusted proxy runs on each machine (or rack) - Proxy aggregates sessions from local processes - Single session from proxy to Chubby (instead of per-process) - Reduces Chubby connection count by 10-100x
Scaling mechanism 4: Cell structure
Google runs multiple Chubby cells: - One local cell per datacenter (low latency) - One global cell for cross-DC data (higher latency, higher availability) - Most clients use local cell - Rare cross-DC coordination uses global cell
The paper shares several hard-won lessons from operating Chubby at Google scale. These insights influenced the design of systems like ZooKeeper.
Lesson 1: Developers abuse everything
Initially, Chubby had no quotas. Developers: - Stored larger and larger files - Created complex directory hierarchies - Used Chubby as a general-purpose file system
Fix: Added strict quotas and size limits. 256KB max file size.
Lesson 2: Caching is not optional
Early Chubby clients could disable caching: - Some developers disabled it "for safety" - This caused thundering herd on restarts - Server couldn't handle direct load
Fix: Made caching mandatory. Clients MUST cache.
Lesson 3: Fine-grained locks don't work
Teams requested fine-grained (millisecond) locks: - Short locks don't amortize RPC overhead - Can't benefit from caching - Better served by different systems (database locks)
Fix: Documented that Chubby is for coarse-grained locks only.
Lesson 4: Lack of quotas = abuse
Without quotas, one misbehaving client affects everyone: - One client creating thousands of files - One client reading without caching - One client holding locks indefinitely
Fix: Per-application quotas, rate limiting, monitoring.
Lesson 5: Publish usage guidelines
Many problems came from incorrect usage: - Using Chubby for data it wasn't designed for - Not watching files, instead polling - Not using ephemeral files for presence
Fix: Extensive documentation, usage reviews.
Apache ZooKeeper is the open-source spiritual successor to Chubby. While inspired by Chubby, ZooKeeper made different design choices.
API differences:
| Feature | Chubby | ZooKeeper | |---------|--------|------------| | Primary abstraction | Locks + Files | Znodes + Primitives | | Lock support | Built-in Acquire/Release | Build from ephemeral + sequential znodes | | Watches | Event subscriptions | One-time triggers (re-register) | | Sessions | KeepAlive + lease | Heartbeat + timeout |
Consistency differences:
Chubby: All operations (reads AND writes) go to master - Linearizable reads - Higher latency for reads - Master can be bottleneck
ZooKeeper: Writes go to leader, reads can go to any server - Reads may be stale (sequential consistency per client) - Lower read latency - Better read scaling - Use `sync()` for linearizable reads
Lock implementation:
Chubby: Explicit lock API
Acquire(handle) // Blocks until lock granted
Release(handle)ZooKeeper: Build locks from primitives
create(/lock/request-, ephemeral, sequential) // Creates /lock/request-0001
getChildren(/lock)
if (lowest sequence number) → lock acquired
else → watch next lowest, wait for deleteTradeoffs: - Chubby: Simpler for lock use cases, consistent, but less flexible - ZooKeeper: More flexible primitives, better read performance, more complex lock implementation
Chubby's influence extends far beyond Google. Its concepts appear in virtually every modern distributed system.
Direct descendants:
Apache ZooKeeper (2008): Yahoo's open-source Chubby-inspired system - Powers Kafka, Hadoop, HBase, Storm - Became the de facto coordination service
etcd (2013): CoreOS's simpler alternative - Uses Raft instead of Paxos - Powers Kubernetes - Simpler API, gRPC interface
Consul (2014): HashiCorp's service mesh coordination - Built-in service discovery - Health checking - Multi-datacenter support
Key concepts that spread:
Modern usage: - Kubernetes: etcd stores all cluster state - Kafka: Originally ZooKeeper, now internal Raft (KRaft) - Hadoop ecosystem: ZooKeeper for HDFS HA, HBase coordination - Service meshes: Consul for service discovery and configuration
Chubby's paper formalized how to build coordination services. Every modern distributed system either uses one of its descendants or implements similar concepts internally.
| Aspect | Advantage | Disadvantage |
|---|---|---|
| Centralized Service vs Library | Fewer servers needed (one cell per DC); consistent, battle-tested implementation; natural metadata storage | Single point of dependency; all services must trust the same Chubby cell; cross-DC coordination requires separate cells |
| Coarse-Grained Locks | Enables aggressive caching; session semantics provide clean failure model; low overhead per lock | Not suitable for fine-grained coordination; lock acquisition latency (seconds) too slow for some use cases |
| Strict Consistency (Master-Only Reads) | Linearizable reads guarantee correctness; simpler reasoning about system behavior | Master is read bottleneck (mitigated by caching); higher read latency than follower-read systems |
| File-System Interface | Familiar abstraction for developers; naturally combines locking with data storage | Tempts developers to use it as general storage; file metaphor may not fit all coordination needs |
| Session-Based Failure Detection | Clean failure model—crash releases all locks and ephemeral files; clients know their state | Session timeout adds latency to failure detection; network partition may cause false positives |