Using Memcached Check-And-Set for Safe Concurrent Updates
Learn how Memcached’s Check-And-Set operation lets multiple clients update the same key safely without external locks, with a worked example and practical limits.
12 Jul 2025, 17:34 UTC

Problem: Concurrent Updates Without Locks
When several application instances need to modify the same cached value – for example, incrementing a request counter or updating a session stamp – a simple get followed by set can lead to lost updates. Two threads may read the same stale value, increment it independently, and write back the same result, causing one increment to disappear. Traditionally this is solved with an external lock or a transactional store, but those add operational complexity and latency.
How Memcached CAS Works
Memcached’s Check‑And-Set (CAS) operation attaches a 64‑bit integer token to each stored item. When a client retrieves a value with gets (or the library equivalent getWithCas), it also receives the current token. To update the item, the client issues a cas command that includes the token; the server only writes the new value if the token still matches the item’s current token. If another client has changed the item in the meantime, the token differs and the server returns a mismatch, signalling that the update must be retried.
Worked Example with Telnet
You can observe the behavior directly with a local memcached instance and telnet. Assume a key counter holding the integer 5.
- Start memcached on port 11211:
memcached -p 11211(run in a terminal with sufficient permissions to bind the port). - Open a telnet session:
telnet localhost 11211. - Retrieve the value and its token:
gets counter VALUE counter 0 1 1234567890123456789 5 END
The line VALUE counter 0 1 1234567890123456789 shows the flags (0), the data length (1 byte), and the CAS token 1234567890123456789.
- Attempt to increment the counter using the same token (this should succeed):
cas counter 0 1 1234567890123456789 6 STORED
The command includes the key, flags (0), expiry (0 – meaning no explicit expiry), data length (1 byte for the digit “6”), the CAS token, and the new value. The server replies STORED because the token matched.
- Now repeat the
getsto obtain a fresh token, then try tocaswith an old token (simulating a concurrent change):
gets counter VALUE counter 0 1 9876543210987654321 6 END cas counter 0 1 1234567890123456789 7 EXISTS
The server returns EXISTS, indicating the CAS token did not match and the update was rejected.
Using CAS in Application Code
Most language clients expose a pair of methods: one to fetch the value together with its CAS token, and another to attempt the update. Below is a language‑agnostic pseudo‑code illustration; replace the calls with the appropriate API for your client (e.g., getWithCas/cas in libmemcached, get with the cas flag in spymemcached, or gets/cas in pymemcache).
function increment_counter(key):
while True:
value, cas_token = client.get_with_cas(key) # blocks until value retrieved
if value is None:
# key missing – initialize to 1
if client.add(key, 1):
return 1
else:
continue # another client added it; retry
new_value = int(value) + 1
success = client.cas(key, new_value, cas_token)
if success:
return new_value
# CAS failed: token changed, retry with fresh value
continue
The loop guarantees that the increment proceeds only when the token matches, eliminating the race condition without an external lock.
Trade‑offs and Limitations
- Extra round‑trip: Each logical update requires a
gets(orgetWithCas) followed by acas. In write‑heavy workloads this can double the number of requests per operation. - Contention handling: If many clients repeatedly clash on the same key, CAS failures may cause busy‑waiting or excessive retries. Implementing a back‑off strategy (e.g., exponential delay with a maximum retry count) is advisable to prevent latency spikes.
- Token validity: The CAS token is only valid while the item remains unchanged and unexpired. If the item expires or is evicted between the
getsandcas, the operation fails with a mismatch, which can surprise long‑running transactions that assume the key persists.
Checking That CAS Is Working
Memcached tracks CAS usage in its statistics. After running a workload that uses CAS, you can verify that the server is processing the commands:
stats ...
Look for the fields cas_hits (successful CAS operations) and cas_misses (failed due to token mismatch). An increase in these counters confirms that the server saw and acted on CAS requests.
Actionable closing: If your application needs to update shared cached values safely, enable CAS in your client library, wrap updates in a retry loop with sensible back‑off, and monitor cas_hits and cas_misses via stats to detect contention. This approach removes the need for a separate locking service while keeping the performance impact modest.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.