Compare-And-Swap (CAS)
A Compare-And-Swap (CAS) operation is critical in a distributed key-value store like Kahuna because it ensures atomic updates and prevents race conditions in environments where multiple clients may try to modify the same key simultaneously. CAS is an atomic operation that:
- Compares a key’s current value (or version) against an expected value or revision
- Only updates the key if the current value matches the expected value or revision
- Fails safely if another process modified the key in the meantime.
Use Case | How CAS Helps |
---|---|
Leader Election | Ensures only one node becomes leader. |
Distributed Locks | Prevents multiple nodes from acquiring the same lock. |
Configuration Updates | Prevents conflicting writes to shared config values. |
Rate Limiting | Ensures atomic updates to request counters. |
Concurrent Transactions | Avoids lost updates when multiple clients modify the same key. |
Advantages of using CAS:
- Ensures atomic updates in distributed stores.
- Prevents race conditions when multiple clients write to the same key.
- Guarantees strong consistency by checking versioning before updating.
- It can be used in leader election, distributed locks, and state coordination.
- Prevents lost updates and ensures correct data modifications.
Examples
The client provides specialized methods to execute CAS operations based on the value or revision:
Distributed Queue Consumer Lock
Use Case: Multiple workers process tasks from a queue but each task should only be handled once
- CLI
- C#
Sets the initial value as usual if it hasn't been assigned previously:
kahuna-cli> set `locks/tasks/123` "node1" ex 10000 nx
r0 set 15ms
Mark the task as completed if this node still hold the key:
kahuna-cli> set `locks/tasks/123` "completed" cmp "node1"
r1 set 11ms
using Kahuna.Client;
public async Task HandleTask(KahunaClient client, string taskId)
{
string keyName = "locks/task/" + taskId;
string machineName = Environment.MachineName;
// Sets the initial value as usual if it hasn't been assigned previously
(bool success, long revision) = await client.SetKeyValue(
keyName,
machineName,
10000,
KeyValueFlags.SetIfNotExists
);
if (!success)
{
Console.WriteLine("Other node acquired the task");
return;
}
// Process the task here
// ...
// Mark the task as completed if this node still hold the key
(success, revision) = await client.TryCompareValueAndSetKeyValue(keyName, "completed", machineName, 0);
if (success)
{
Console.WriteLine("Marked as completed successfully");
return;
}
}
Feature Flag Update Control
Use Case: Update a feature flag, but only if it hasn’t changed since you last read it.
- CLI
- C#
Obtains the current value of the feature flag:
get `feature/new-landing`
r0 disabled 12ms
Change the flag if the revision is still 0:
kahuna-cli> set `feature/new-landing` "enabled" cmprev 0
r1 set 19ms
The flag won't change if the revision is not 0:
kahuna-cli> set `feature/new-landing` "disabled" cmprev 0
r1 not set 14ms
using Kahuna.Client;
public async Task CheckFeatureFlag(KahunaClient client, string featureName)
{
string featureKey = "feature/" + featureName;
// Obtains the current value of the feature flag
(byte[]? _, long revision) = await client.GetKeyValue(featureKey);
// ...
// Check if the flag was changed
(success, revision) = await client.TryCompareRevisionAndSetKeyValue(featureKey, "enabled", revision);
if (success)
{
Console.WriteLine("No one else changed the flag between our read and write.");
return;
}
}