Distributed Locks
A distributed lock is a mechanism that ensures that a specific resource is accessed by only one node or process at a time in a distributed environment. This is crucial when:
- Preventing race conditions: Ensuring that multiple processes do not modify shared resources simultaneously.
- Coordinating tasks: Managing access to shared databases, files, or services across different nodes.
- Maintaining data durability: Guaranteeing that concurrent operations do not result in inconsistent states.
By partitioning locks among nodes controlled by Raft Groups, Kahuna offers:
- Reliability: Raft consensus ensures that partition data remains consistent even in the face of network failures.
- Simplicity: A straightforward API based on leases makes it easy to integrate distributed locking into your applications.
Use Cases
- Leader Election: Elect a single leader in a cluster of services. Only one node should act as the leader at any time (e.g., for scheduling, replication). A distributed lock ensures only one process "wins" and holds the leadership.
- Preventing Double Execution of Scheduled Jobs: Ensure a cron job or background worker is executed only once across multiple nodes. In a horizontally scaled system, multiple nodes might try to run the same job. A distributed lock prevents multiple executions of the same scheduled task.
- Safe Deployment CI/CD Coordination: Prevent multiple CI/CD pipelines from deploying the same environment simultaneously. Two deployment jobs could conflict and cause downtime. Use a lock to serialize deployments.
- Database Migration Coordination: Ensure that only one service performs schema migration at startup. If multiple services run migrate up concurrently, it may corrupt the schema. Use a lock to ensure only the first instance runs migrations.
- Session Control / Login Exclusivity: Allow only one active session per user. Used in banking apps, gaming, admin consoles, etc.
- Distributed Queue Consumer Coordination: Ensure that a message from a queue is only processed once, even with multiple consumers.
- Throttling or Rate-Limiting Across Services: Enforce a global rate limit across many service instances.
- External System Coordination: Ensure only one node writes to an external system (e.g., shared database, billing API, payment gateway) to avoid double charges or inconsistent writes.
API
Kahuna exposes a simple API for acquiring, releasing and extending locks. The main functions are:
Lock
- API
- CLI
- C#
- Rest
(bool Locked, long FencingToken) TryLock(string resource, string owner, int expiresMs, Durability durability);
- resource: The identifier for the resource you want to lock.
- owner: A unique identifier for the lock, usually associated with the client or process requesting the lock.
- expiresMs: The expiration time for the lock in milliseconds.
- durability: Defines whether the lock durability is Ephemeral or Persistent.
Returns:
- Locked:
true
if the lock was successfully acquired. - FencingToken: A global counter indicating the number of times the lock has been acquired.
Acquire a lease on the resource locks/tasks/123
for 10 seconds in the interactive shell:
kahuna-cli> lock locks/tasks/123 10000
f1 acquired 786f947d8a6643f0b939865f72aa512a
Acquire a lease using command line arguments:
~> kahuna-cli --lock locks/tasks/123 --expires 10000
f4 acquired 4e3c5d8ee76a4a1db268c81b048a002a
Below is a basic example to demonstrate how to acquire a distributed lock in a C# project:
await using KahunaLock myLock = await client.GetOrCreateLock(
"balance-" + userId,
TimeSpan.FromSeconds(5)
);
if (myLock.IsAcquired)
{
Console.WriteLine("Lock acquired!");
// implement exclusive logic here
}
Acquiring a lock from the Rest API:
curl --location 'https://kahuna-dev.company.internal/v1/locks/try-lock' \
--header 'Content-Type: application/json' \
--data '{"resource":"locks/tasks/123", "owner":"e5943062358144b4b0bbff8868f7063d", "expiresMs":10000, "durability":0 }'
Response:
{"type": 0, "fencingToken": 0}
Unlock
- API
- CLI
- C#
- Rest
(bool Unlocked) Unlock(string resource, string owner, Durability durability);
- resource: The identifier for the resource to unlock.
- owner: The unique identifier for the lock previously used to acquire the lock.
- durability: Defines whether the lock durability is Ephemeral or Persistent.
Returns:
- Unlocked:
false
if the resource was successfully unlocked.
Unlock the acquired lease on the resource locks/tasks/123
in the interactive shell:
kahuna-cli> lock locks/tasks/123 10000
f1 acquired 786f947d8a6643f0b939865f72aa512a
kahuna-cli> unlock locks/tasks/123
f1 unlocked
Unlock the acquired lease using command line arguments:
$ kahuna-cli --lock locks/tasks/123 --expires 60000
f4 acquired 4e3c5d8ee76a4a1db268c81b048a002a
$ kahuna-cli --unlock-lock locks/tasks/123 --owner 4e3c5d8ee76a4a1db268c81b048a002a
f4 unlocked
Below is a basic example to demonstrate how to acquire a distributed lock in a C# project.
KahunaLock myLock = await client.GetOrCreateLock(
"balance-" + userId,
TimeSpan.FromSeconds(5)
);
if (myLock.IsAcquired)
{
Console.WriteLine("Lock acquired!");
// implement exclusive logic here
}
// Manually release the lock
await myLock.DisposeAsync();
curl --location 'http://kahuna-dev.company.internal/v1/locks/try-unlock' \
--header 'Content-Type: application/json' \
--data '{"resource":"locks/tasks/123", "owner":"e5943062358144b4b0bbff8868f7063d", "durability":0}'
Response:
{"type": 0, "fencingToken": 0}