Skip to content

avifenesh/glide-distributed-lock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Distributed Lock for Valkey Cluster

A robust, production-ready distributed lock implementation for Valkey cluster using TypeScript. This library provides thread-safe distributed locking with automatic deadlock prevention and cluster-aware routing.

Features

  • đź”’ Atomic Operations: Lock acquisition and release are atomic using Lua scripts
  • ⏰ Automatic Expiration: Locks automatically expire to prevent deadlocks
  • 🎯 Cluster-Aware: Optimized routing for Valkey cluster deployments
  • 🛡️ Ownership Verification: Only lock owners can release their locks
  • 🚀 High Performance: Minimal overhead with efficient Lua script execution
  • 📝 TypeScript Support: Full type safety and IDE support

Installation

npm install @valkey/valkey-glide

Quick Start

import { DistributedLock, createValkeyClusterClient } from "./distributed-lock"; async function main() { // Connect to Valkey cluster const client = await createValkeyClusterClient([ { host: "localhost", port: 7000 }, { host: "localhost", port: 7001 }, { host: "localhost", port: 7002 } ]); // Create lock instance const lock = new DistributedLock(client); // Safe set operation with lock protection const success = await lock.safeSet("user:123", "John Doe", 3600); console.log(`Operation ${success ? "succeeded" : "failed"}`); await client.close(); }

API Reference

DistributedLock

Constructor

new DistributedLock(client: GlideClusterClient, lockTtlSeconds?: number)
  • client: Valkey cluster client instance
  • lockTtlSeconds: Lock expiration time in seconds (default: 10)

Methods

safeSet(key: string, value: string, ttl?: number | "*"): Promise<boolean>

Safely set a key-value pair with distributed lock protection.

const success = await lock.safeSet("user:123", "data", 3600);
acquireLock(key: string): Promise<boolean>

Manually acquire a lock for a given key.

const acquired = await lock.acquireLock("critical_section"); if (acquired) { // Do critical work await lock.releaseLock("critical_section"); }
releaseLock(key: string): Promise<boolean>

Release a lock (only if owned by this client).

const released = await lock.releaseLock("critical_section");
withLock<T>(key: string, fn: () => Promise<T>): Promise<T | null>

Execute a function with exclusive lock protection.

const result = await lock.withLock("database_migration", async () => { // Critical work here return "completed"; });
getClientId(): string

Get the unique client identifier for this lock instance.

const clientId = lock.getClientId();

Usage Examples

Example 1: Basic Lock Usage

import { DistributedLock, createValkeyClusterClient } from "./distributed-lock"; const client = await createValkeyClusterClient([ { host: "localhost", port: 7000 } ]); const lock = new DistributedLock(client); // Acquire lock, do work, release lock const acquired = await lock.acquireLock("resource_1"); if (acquired) { console.log("Lock acquired, doing critical work..."); // Perform critical operations await lock.releaseLock("resource_1"); }

Example 2: Safe Operations

// This automatically handles lock acquisition and release const success = await lock.safeSet("config:app", JSON.stringify({ setting1: "value1", setting2: "value2" }), 7200); // 2 hours TTL if (success) { console.log("Configuration updated successfully"); }

Example 3: Function Execution with Lock

const result = await lock.withLock("user_counter", async () => { // Get current count const current = await client.get("counter") || "0"; const newValue = (parseInt(current) + 1).toString(); // Set new count await client.set("counter", newValue); return newValue; }); if (result) { console.log(`New counter value: ${result}`); } else { console.log("Could not acquire lock"); }

Example 4: Concurrent Access Protection

// Multiple operations attempting to update the same resource const operations = [ lock.safeSet("shared_resource", "operation_1"), lock.safeSet("shared_resource", "operation_2"), lock.safeSet("shared_resource", "operation_3") ]; const results = await Promise.all(operations); const successCount = results.filter(Boolean).length; console.log(`${successCount} operations succeeded`);

Architecture

Lock Keys

Locks use the pattern lock:{key} to avoid conflicts with your application data.

Atomic Release

Lock release uses a Lua script to ensure atomicity:

if server.call("get", ARGV[1]) == ARGV[2] then return server.call("del", ARGV[1]) else return 0 end

This ensures only the lock owner can release the lock.

Cluster Routing

Operations are automatically routed to the appropriate cluster nodes:

  • Lock operations target the primary node for the lock key
  • Read operations can use replica preference for better performance

Best Practices

  1. Keep Lock Duration Short: Use the shortest possible lock duration for your use case
  2. Handle Lock Acquisition Failures: Always check if lock acquisition succeeded
  3. Use withLock for Simple Cases: It automatically handles cleanup
  4. Monitor Lock Contention: High contention may indicate design issues
  5. Set Appropriate TTL: Balance between deadlock prevention and operation time

Error Handling

try { const success = await lock.safeSet("key", "value"); if (!success) { console.log("Could not acquire lock - another operation in progress"); // Handle lock contention } } catch (error) { console.error("Lock operation failed:", error); // Handle connection or other errors }

Testing

Run the example to test the implementation:

npm run build && node dist/example.js

Requirements

  • Valkey cluster (6+ nodes recommended for production)
  • Node.js 16+
  • TypeScript 4.5+

License

Apache 2.0 License - see LICENSE file for details.

About

A robust distributed lock implementation for Valkey cluster

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published