Prevent Lost Updates in Memcached with CAS Tokens
Use gets and cas to update shared memcached items safely without server-side locks.
14 Jun 2026, 23:30 UTC

The problem: silent lost updates
If two application clients read the same memcached key, change the value locally, and write it back with set, the second writer overwrites the first. Memcached does not lock a key between separate connections, so the first change disappears. This is a lost update, and it commonly affects shared counters, configuration flags, and session counters.
Useful takeaway
Use the gets and cas commands together for optimistic compare-and-swap. gets returns a unique cas token with the current value. cas stores a new value only if that token still matches the item in memory. If another client changed the item in the meantime, memcached replies EXISTS and writes nothing.
How the mechanism works
A cas token is a server-generated 64-bit identifier attached to a cached item. The token changes whenever the item is modified, expires, or is evicted. Between gets and cas, memcached does not hold a lock. Instead, it validates the token at write time. This makes CAS optimistic: it works well when conflicts are rare, but callers must retry when they see EXISTS.
Worked example: update a shared flag
Run the following from a terminal that can reach the memcached host, usually on TCP port 11211. No special operating-system permissions are required beyond opening that network connection. Replace placeholders in angle brackets with your actual values.
# Step 1: read the current value and its cas token gets # Example server response: # VALUE # # END
The VALUE line contains the item flags, byte length, and the cas token. Flags are an opaque 32-bit integer stored alongside the value; exptime is the expiration time in seconds.
# Step 2: compute the new value locally, then attempt the conditional write cas # Expected replies: # STORED -> token matched; value was updated # EXISTS -> another client changed the item; no write occurred # NOT_FOUND -> item expired or was evicted
If the reply is EXISTS, restart from gets with a fresh token. Do not reuse the old token; it is invalidated by the competing write.
What happens during a conflict
Imagine two clients, A and B. Both run gets session:123 and receive the same cas token. Client A runs cas first and receives STORED. Client B then runs cas with the old token and receives EXISTS. Client B must read the item again, apply its change to the new value, and retry. If clients retry immediately in a tight loop under heavy contention, they can create a retry storm. For simple counters, use incr or decr instead, which are atomic without CAS tokens.
Limits and common mistakes
- Do not pair
getwithsetfor shared state. Plaingetreturns no cas token, sosetcannot detect intervening changes. - Do not cache a cas token across a long delay. Expiration, eviction, or any modification invalidates it. A delayed
caswill almost always returnEXISTS. - Item size is limited to 1 MB by default. Adjust with the
-Istartup flag if needed; CAS does not change eviction or TTL behavior. - Confirm ASCII protocol is available.
getsandcasare ASCII-protocol commands. Binary-only deployments use the binary compare-and-swap operation instead, and some client libraries hide raw tokens behind atomic increment or append helpers. - High contention on one key causes repeated
EXISTSreplies. Shard the key across multiple keys or redesign so hot items are updated by a single writer.
Verify the behavior
Start memcached with default ASCII support. From one client, run gets mykey and confirm the response includes a cas integer. From a second client, modify mykey between the first client's gets and cas. The first client's cas should return EXISTS, while the second client's write returns STORED. Check the server version with memcached -h or stats settings; this pattern applies to the 1.6 and 1.7 stable series.
Rollback
No rollback is required for a rejected cas: because the token did not match, memcached made no change. If a cas succeeds and you need to undo it, run a fresh gets to obtain the current token, then issue a new cas with the previous value. The old token from before the successful write will no longer work.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.