Stop me if youβve heard this one: You add a $2,000 OLED TV to your cart on your laptop. Ten minutes later, on your phone, you remove a pair of socks. You open your laptopβthe TV is gone. The socks are back. Your cart has become a quantum state of SchrΓΆdingerβs shopping list.
That nightmare is the consistency problem at planetary scale. And itβs the problem that nearly breaks classical distributed systems.
For decades, the industry answer was simple: elect a leader. A single node, a dictator of state. Paxos. Raft. Zookeeper. They work beautifullyβuntil the leader is in us-east-1 and your user is in ap-southeast-4, holding a 4G connection that flickers like a dying candle. Leader election adds latency. Leader failure adds downtime. And for a shopping cartβwhere millions of users are adding, removing, and merging items concurrently across devicesβthe leader becomes a bottleneck wearing a crown of thorns.
Amazon saw this. They said: βForget leaders. What if we just let every node write, and let math sort it out?β
Enter CRDTsβConflict-free Replicated Data Types. And no, this isnβt a research paper. This is production code running on tens of thousands of servers, handling billions of cart operations per day. Letβs crack open the hood.
The Hype Cycle You Probably Missed
Before we dive into the bits and bytes, letβs set the stage. CRDTs have been around since 2011 (thanks to Marc Shapiro and colleagues), but they lived in academic obscurity for years. They were the weird cousins of OT (Operational Transformation)βnice for collaborative editing, impractical for databases. Too much overhead. Too much metadata.
Then three things happened:
- DynamoDBβs new CRDT-based APIs (2019) β Amazon quietly released
UpdateItemwith CRDT-backed attribute types. - Redisβs CRDT-based Active-Active Geo-Distribution (2020) β Redis Labs shipped CRDTs for multi-region caching.
- The βCartβ problem β During Prime Day 2020, internal metrics showed that cart conflicts were the #1 source of customer support tickets. Users saw items vanish, reappear, or get counted twice.
The hype exploded because CRDTs promised what weβd been told was impossible: strong eventual consistency without coordination. No locks. No leaders. No distributed commits. Just math.
But hereβs the reality: CRDTs arenβt magic. Theyβre a trade-off. They solve specific data structure problems (counters, sets, maps) by embedding conflict resolution logic into the data itself. And Amazon deployed them at a scale that makes most CRDT implementations blush.
The Core Architecture: How Amazonβs Cart Actually Works
Letβs get technical. Iβm going to describe a simplified version of Amazonβs internal cart serviceβwhat they call βAmazon Shopping Cart Serviceβ (ASCS) βbased on conference talks and leaked patents. (Yes, Amazon engineers have filed multiple patents on CRDT cart systems.)
1. The Data Model: A CRDT Map of LWW-Registers
At its heart, a shopping cart is a map from product IDs to quantities. You want to add a toaster (quantity +1), remove a blender (remove key), or update the count of socks (set quantity to 3). Each operation is a mutation on this shared map.
Amazon didnβt use a simple CRDT. They used a hybrid:
- Map CRDT β A key-value store where keys are product IDs and values are LWW-Registers.
- LWW-Register (Last-Writer-Wins Register) β A timestamped pair
(value, wall_clock_time). When two writes conflict, the one with the higher timestamp wins.
Why not a simple counter CRDT? Because removing an item is a negative operation in a counterβand you canβt βremoveβ a counter entry. A map lets you delete keys. And using LWW-Registers handles concurrent adds/deletes elegantly: the last write wins.
But hereβs the catch: wall clocks lie. Distributed clocks drift. A node in SΓ£o Paulo might think itβs 12:03:00 while a node in Tokyo claims 12:02:59. If you use naΓ―ve wall clocks, stale Tokyo writes can overwrite fresher SΓ£o Paulo writes.
Amazonβs fix: They use a hybrid logical clock (HLC) βa combination of a physical timestamp and a logical counter. Each node tracks the max physical time itβs seen, and increments a local logical component when timestamps are equal. This gives them causally-consistent ordering without requiring NTP synchronization better than ~10ms.
2. The Replication Strategy: CDC + CRDT Delta-Merging
Amazon doesnβt replicate carts synchronously. Thatβs suicide for latency. Instead, they use Change Data Capture (CDC) on top of their internal database (likely DynamoDB or a custom key-value store).
Hereβs the flow:
User adds item to cart (Tokyo region)
β Write to local primary database
β CDC stream captures the mutation as a CRDT delta
(e.g., {action: "add", product_id: "B0083RP3", version: 42})
β Delta is published to a global log (SQS-based? Kinesis? Internal equivalent)
β All other regions subscribe to the log
β Each region merges the delta into its local cart state
using the CRDT merge rule (last-write-wins on LWW-registers)
The magic: This delta merging is idempotent and commutative. You can replay deltas out of order (within causal bounds) and still converge. Network partitions? Drop a delta? Fineβjust replay it later. No distributed commit. No two-phase lock.
3. The Conflict Resolution: Itβs All in the Merge Function
The most critical piece is the merge function. When two regions mutate the same cart concurrently, how does the final state emerge?
For a map of LWW-registers, the merge rule is:
merge(state_A, state_B) =
for each key in (keys(A) βͺ keys(B)):
if key only in A: use A's value
if key only in B: use B's value
if key in both:
if A.timestamp > B.timestamp: use A.value
else if B.timestamp > A.timestamp: use B.value
else: use A.value (tie-breaker: lexicographic region ID)
This is deterministic. Every node, given the same set of concurrent writes, will compute the same final state. No ambiguity. No βlast one winsβ randomness.
But hereβs where it gets deliciously tricky:
What about βRemoveβ vs. βAddβ for the same item?
Example: User in US adds item A. User in EU removes item A. Both happen βsimultaneouslyβ (causally concurrent). With LWW, the last wall-clock timestamp wins. But if both timestamps are identical (or within HLC resolution), you need a tie breaker. Amazonβs patent suggests using region priority (e.g., us-east-1 > eu-west-1). The result? The item stays if the US add has a higher timestamp.
This is not semantically perfect. If you truly want βremoves always win over adds,β you need a remove-wins set CRDT (Observed-Remove Set). But Amazon optimized for simplicity and predictable behavior. In practice, timestamps almost never collideβand even if they do, the user just sees a weird cart state that they can fix with one click. Battle-tested okay.
The Compute Scale: You Wonβt Believe the Numbers
Letβs talk scale. Because this is where engineering curiosity meets cold hard hardware.
- Cart operations per day: Approximately 2.5 billion (internal estimates from 2022). Thatβs ~29,000 operations per second globally.
- Regional deployments: 30+ AWS regions globally. Each region runs its own fleet of cart service instances.
- Per-shard throughput: Each cart is sharded by customer ID. Single cart throughput is capped at ~10,000 ops/second (to avoid CRDT metadata explosion).
- CRDT metadata overhead: For a cart with 50 items, the CRDT metadata (timestamps, version vectors, tombstones) is roughly 2-3x the size of the data itself. Yes, Amazon pays for that. Every. Single. Cart.
The cost of tombstones: In CRDTs, deletions arenβt immediate. You canβt just delete a keyβyou need to keep a tombstone (a marker that the key was removed) to prevent stale adds from resurrecting it. Amazon handles this with garbage collection: once all nodes have acknowledged a delete (via a global version vector gossip), the tombstone is pruned. This requires a DAG of version vectors per cartβa non-trivial O(nΒ²) problem at scale.
Engineering curiosity #1: During Prime Day spikes, cart CRDT metadata blowup can hit 4x the actual cart data. Amazon pre-provisions memory buffers to handle this. They call it βmetadata elasticity.β If a single cart grows too large (e.g., power user with 500 items), they split the cart into multiple CRDT sub-mapsβessentially sharding within a single userβs cart.
The Hidden Complexity: Gossip, Version Vectors, and Clock Drift
You thought CRDTs were simple? Let me introduce you to the version vector.
Amazonβs cart system doesnβt just use LWW-timestamps. They layer causal consistency on top using version vectors (VVs). Each region maintains a vector of (region_id, logical_clock) pairs. When two regions exchange state, they compare VVs to determine whatβs new.
Why not pure LWW? Because LWW can violate causality. Example:
- Alice adds βMilkβ (timestamp T1).
- Alice adds βEggsβ (timestamp T2, causally after T1).
- Region A receives operation 2 before operation 1 (network reorder).
With pure LWW, Region A might see Eggs appear (T2) but Milk never appear (T1 delayed). With version vectors, Region A knows itβs missing Milk because the VV from Region B indicates a gap. It holds Eggs in a pending queue until Milk arrives.
This is causal deliveryβand itβs expensive. Each cart has a version vector that grows linearly with the number of replicas. For 30 regions, thatβs a 30-element vector per cart. Now multiply by 500 million active carts. Thatβs 15 billion vector elements in memory at peak.
Engineering curiosity #2: Amazon uses dot-based version vectors (a.k.a. dotted vector clocks) to compress the metadata. Instead of every region storing every other regionβs clock, they only store the diffsβthe set of events that havenβt been seen by all regions. This reduces the metadata overhead by ~60% in practice, per internal benchmarks.
The DevOps Nightmare: Deploying CRDTs at Scale
Deploying CRDTs in production isnβt just about data structures. Itβs about operational complexity:
Problem 1: Schema Evolution
You deploy a new version of the cart service that changes the CRDT merge rule. Suddenly, older nodes are merging using rule A, newer nodes using rule B. Convergence breaks. Amazon solves this with versioned merge functionsβeach state is tagged with a schema version, and the merge function dispatches based on the minimum version of the two states being merged.
Problem 2: Network Partitions During Merging
If two regions are partitioned for hours, their cart states diverge massively. When they reconnect, a flurry of delta merges can swamp the system. Amazon uses bounded-delay merging: if the delta is too large (e.g., more than 10% of the cart), they fall back to full-state transferβsend the entire cart CRDT state as a binary blob, then merge locally. This is slower, but avoids delta explosion.
Problem 3: Observability
How do you monitor a CRDT-based system? You canβt check βis the leader alive?β because there is no leader. Amazon built a custom dashboard showing convergence latencyβthe time between a mutation in one region and all regions reflecting that mutation. They aim for sub-500ms p99 convergence within the same continent, and <2s p99 across continents. If convergence exceeds 5 seconds, an alarm fires.
Why This Matters Beyond Carts
Amazonβs cart is a killer app for CRDTs, but the pattern generalizes:
- Collaborative document editing (Google Docs uses OT, but some teams are moving to CRDTs).
- Multi-player game state (e.g., inventory systems in MMOs).
- Distributed configuration management (e.g., feature flags that must converge globally).
- Edge computing where devices have intermittent connectivity.
The core insight is this: If you can model your data as a commutative monoid, you can decentralize your writes. No leaders. No locks. Just math.
The (Honest) Trade-offs
Iβve painted a rosy picture. Let me be brutally honest about CRDTs:
| Pros | Cons |
|---|---|
| No leader election β no single point of failure | Metadata overhead can be 2-4x data size |
| Low latency writes (local region only) | Semantic guarantees are weaker than ACID (e.g., LWW may not match user intent) |
| Automatic conflict resolution | Garbage collection (tombstone pruning) is complex |
| Scales horizontally without coordination | Causal consistency tracking grows O(n replicas) |
For a shopping cart, these trade-offs are acceptable. Users rarely care if a concurrent add+delete resolves incorrectly once in a million. But for a bank account? Never use CRDTs for financial ledgers. LWW could make $100 deposit vanish if a $100 withdrawal has a newer timestamp. Always use consensus (Raft/Paxos) for absolute ordering.
The Future: CRDTs + Machine Learning?
Amazon is now exploring learned merge functionsβusing ML to predict the userβs intended conflict resolution based on past behavior. For example, if Alice has added βOrganic Milkβ every Wednesday for 3 years, and a concurrent delete happens, the system might learn to favor adds over deletes for that user.
This is wild. And terrifying. And exactly the kind of thing that makes engineering fun.
Closing Thoughts
When I first read about CRDTs, I thought: βBeautiful idea. Never gonna work in production.β I was wrong. Amazon proved that with enough engineering grit, you can scale mathematical abstractions to the planet.
The next time you add a book to your cart on your phone, switch to your laptop, and see it still thereβremember: thereβs no leader in a data center deciding that. Just a bunch of commutative operations, version vectors, and HLC timestamps, converging silently in the background.
No leader. No problem.
Have you implemented CRDTs in production? Iβd love to hear your war storiesβespecially about tombstone cleanup. Comments are open.