Dynamo's Bargain: Keeping Amazon Services Writable Under Failure

This series examines Dynamo, Amazon's highly available key-value store, through one practical question: how can a service keep accepting useful state changes when servers, network routes, or even datacenters are unavailable? The answer is not to make every replica agree immediately. Dynamo keeps the write path open, accepts that replicas may temporarily disagree, and then uses versioning, application decisions, and background repair to make that disagreement manageable. Understanding that bargain lets us judge both what Dynamo achieved and where its design stops applying.

The storage problem inside a service network

Amazon's platform was not one monolithic application waiting on one database. It was a collection of hundreds of services, some stateless and some responsible for persistent state. A page request could depend on many services in sequence: recommendations, product information, session state, shopping-cart contents, and other pieces of business logic. If one stateful dependency became unavailable, the effect could reach the customer even when the rest of the platform was operating normally.

That setting changes the meaning of reliability. A storage system does not serve an abstract record; it holds the state that lets a service complete its job. A shopping cart is a clear example. A customer should be able to add or remove an item even if one storage host is down, a network route is unstable, or a datacenter is temporarily unreachable. Rejecting the update may be more damaging than allowing it to be written somewhere and reconciling it later. For this class of service, “available” means more than being able to answer reads. It means remaining writable when the environment is uncertain.

Dynamo was shaped for that need, but its scope was deliberately narrow. It stores relatively small binary objects identified by unique keys and exposes single-item reads and writes. It does not try to provide relational queries, operations spanning several items, or isolation between transactions. Each service can run its own Dynamo instance and configure it for its own workload. That makes the system a poor universal replacement for a relational database, but an efficient fit for services whose state access already looks like “read this object by key” or “replace this object by key.”

The contrast with a conventional strong-consistency-first design is important. When replicas cannot communicate, a system that promises immediate agreement may have to refuse an operation because it cannot establish which answer is correct. Dynamo makes a different choice for its target applications: preserve the customer's update, even if the system must expose uncertainty about the current complete state. This does not make consistency irrelevant. It changes where consistency work happens and who must participate in it.

flowchart LR A["Amazon service request"] --> B["Replicated key-value state"] B --> C{"Replica or network failure?"} C -- "no" --> D["Replicas accept or read the value"] C -- "yes" --> E["Write remains possible"] E --> F["Replicas may diverge"] F --> G["Application or system reconciliation"] G --> H["Updates converge"]

The bargain: write now, reconcile later

Dynamo is eventually consistent. Updates are propagated to replicas asynchronously, so a read can temporarily return a value that is not the newest update. “Eventually” is therefore a condition about convergence, not a promise of immediate visibility. Under a failure or partition, a successful write can exist at some replicas while other replicas still hold an older value or no reachable copy at all.

For an always-writeable service, that temporary uncertainty can be useful. Suppose the latest version of a shopping cart is on a host that cannot currently be reached. A customer adds an item to an older version. Dynamo does not simply discard the operation because the newest state is unavailable. The addition remains meaningful, while the newer and older states can be reconciled when they meet. The same logic applies to removal operations, although merging divergent versions can allow a previously deleted item to reappear. Availability preserves the opportunity to decide correctly later; it does not guarantee that the later decision will be trivial.

The interface makes this responsibility visible. A get() can return one object, or it can return several versions that the system cannot order as ancestor and descendant. Along with the value, the client receives an opaque context containing version information. A subsequent put() supplies that context, allowing Dynamo to determine what the client actually read before attempting the update. The context is not a business-level answer. It is the evidence the storage layer needs to reason about the history of the object.

Some version relationships can be handled by the system itself. If a new version causally follows an older one, the older version has been superseded and can eventually be forgotten. But two writes made from separate branches may be causally unrelated. Dynamo can detect that relationship, but it cannot know whether the right business response is to merge two shopping carts, choose one session value, or apply some other rule. That is semantic reconciliation, and it belongs to the application because the application understands what its data means.

This division of labor is one of Dynamo's defining choices. A storage layer can apply a general rule such as “last write wins,” but a general rule may be wrong for an operation where losing an update is unacceptable. A shopping-cart service can merge the contents of two versions because adding an item is meaningful even when it was made from an older view. A session service may prefer the value with the latest physical timestamp because its state has different semantics. Dynamo gives both services a way to operate, but it asks each service owner to understand the consequences.

The cost is not confined to an occasional complicated read. Conflict handling can add work to application code, reads may return more than one value, and later repair consumes storage and network resources. The system also has to preserve enough history to tell ordinary staleness from genuine concurrency. Dynamo's design is therefore not “ignore consistency.” It is “keep writes moving, record enough information to understand divergence, and resolve meaning at the boundary where meaning is known.”

A mental map of the system

The mechanisms that support this bargain fit together as a chain. First, Dynamo partitions the key space with consistent hashing so that a key can be assigned to a responsible part of the ring. Virtual nodes spread that responsibility across many positions, helping the system redistribute load when a host fails or when a new host joins. The point is not only faster lookup. It is the ability to change capacity incrementally without rebuilding the entire placement of data.

Next, Dynamo replicates each key on several hosts and constructs a preference list of distinct physical nodes. Replicas can be placed across datacenters, so the loss of one facility need not make the data unavailable. When a preferred node is temporarily unreachable, Dynamo can use another healthy node and later return the displaced replica to its intended home. These choices make continued progress possible, but they also explain why a successful request does not prove that every replica is already identical.

The system exposes some of the remaining choices through N, R, and W: the number of replicas, the minimum responses required for a read, and the minimum responses required for a write. Those settings affect availability, durability, consistency exposure, and latency together. Waiting for more replicas can reduce a vulnerability window but makes an operation more dependent on reachable and responsive hosts. Waiting for fewer replicas can keep the service responsive but leaves more convergence work behind. Later, the system's version clocks, read repair, hinted handoff, and anti-entropy mechanisms address that deferred work.

Latency adds another dimension. Amazon measured service agreements at high percentiles such as the 99.9th percentile because a small slow tail can damage the customer experience, especially when one page request depends on many services. Dynamo operations involve multiple replicas, so the slowest required response can dominate the result. A design that looks fast on average may still violate the service-level target. This is why coordinator placement, storage choices, buffering, and the scheduling of background repair all matter alongside the basic availability decision.

There are also clear boundaries. The paper describes Dynamo as an internal system in a trusted environment, not as a security boundary with authentication and authorization. Its initial design targeted instances with up to hundreds of storage hosts and used a full membership view that every node could gossip to its peers. It did not provide isolation or multi-item transactions. These are not incidental omissions to be filled in by assumption; they define the environment in which the reported design was intended to work.

Dynamo's central lesson is consequently more precise than “eventual consistency scales” or “availability is better than consistency.” Amazon chose a workload where an accepted update could be more valuable than an immediately agreed value. That choice required a complete supporting structure: replicas to survive failure, placement rules to spread risk, version metadata to expose causality, application logic to resolve meaning, repair processes to restore convergence, and latency controls to keep recovery from damaging the foreground service.

The next question is how those replicas and versions behave in detail when a request meets a partial failure. Once that path is visible, the relationship between Dynamo's availability promise and its conflict-resolution work becomes concrete rather than abstract.

References