Production-ready distributed locks for Redis and Valkey with support for node-redis, ioredis, and Valkey GLIDE
npm install redlock-universal> Production-ready distributed locks for Redis and Valkey with support for
> node-redis, ioredis, and Valkey GLIDE






redlock-universal implements distributed Redis locks using the
Redlock algorithm. It supports both
node-redis and ioredis clients through a unified TypeScript API with automatic
lock extension capabilities.
> **
> NestJS Integration:** Check out
> nestjs-redlock-universal
> for decorator-based integration with dependency injection.
- π Distributed Locks: True Redlock algorithm for multi-instance Redis
- π Client Universal: Works with both node-redis v4+ and ioredis v5+
- π€ Auto-Extension: using() API with automatic lock extension for
long-running operations
- π Structured Logging: Comprehensive Logger integration for production
observability
- π’ Production Ready: Circuit breakers, health checks, error handling, and
retries
- π TypeScript First: Full type safety and modern ESM support
- β‘ Performance: Sub-millisecond lock acquisition (0.48ms mean), fastest
throughput among tested libraries (3300+ ops/sec)
- π Enhanced Monitoring: Built-in metrics, health checks, and structured
logging
- π§ͺ Tested: 86%+ test coverage with 456 unit, integration, and E2E tests
- π· Valkey Ready: First-class support for Valkey 8+ with official GLIDE client adapter
- Quick Start
- Core Concepts
- API Reference
- Auto-Extension
- Examples
- Migration Guide
- FAQ
- Troubleshooting
``bash`
npm install redlock-universal
Peer Dependencies: Install your preferred Redis client
`bashFor node-redis users
npm install redis
> Valkey Users: See VALKEY.md for detailed Valkey setup guide.
Quick Start
$3
`typescript
import { createLock, NodeRedisAdapter } from 'redlock-universal';
import { createClient } from 'redis';// Setup Redis client
const client = createClient({ url: 'redis://localhost:6379' });
await client.connect();
// Create lock
const lock = createLock({
adapter: new NodeRedisAdapter(client),
key: 'my-resource',
ttl: 30000, // 30 seconds
});
// Automatic lock management - the easy way
await lock.using(async signal => {
await processData();
// Lock auto-extends if needed, releases automatically
// Check signal.aborted if you need to know about extension failures
});
`$3
`typescript
// Traditional approach (if you need fine control)
try {
const handle = await lock.acquire(); // Critical section - only one process can be here
await doSomeCriticalWork();
await lock.release(handle);
} catch (error) {
console.error('Lock operation failed:', error);
}
`$3
`typescript
import { GlideClient } from '@valkey/valkey-glide';
import { createLock, GlideAdapter } from 'redlock-universal';// Setup Valkey GLIDE client
const client = await GlideClient.createClient({
addresses: [{ host: 'localhost', port: 6379 }],
});
const adapter = new GlideAdapter(client);
const lock = createLock({ adapter, key: 'my-resource', ttl: 30000 });
// Automatic lock management with auto-extension
const result = await lock.using(async (signal) => {
// Your critical section here
return await processData();
});
`$3
`typescript
import {
createRedlock,
NodeRedisAdapter,
IoredisAdapter,
} from 'redlock-universal';
import { createClient } from 'redis';
import Redis from 'ioredis';// Setup multiple Redis connections
const clients = [
createClient({ url: 'redis://redis1:6379' }),
createClient({ url: 'redis://redis2:6379' }),
createClient({ url: 'redis://redis3:6379' }),
];
// Connect all node-redis clients
await Promise.all(clients.map(client => client.connect()));
// Create adapters (ioredis connects automatically)
const adapters = [
new NodeRedisAdapter(clients[0]),
new NodeRedisAdapter(clients[1]),
new IoredisAdapter(new Redis('redis://redis3:6379')),
];
// Create distributed lock
const redlock = createRedlock({
adapters,
key: 'distributed-resource',
ttl: 30000,
quorum: 2, // Majority consensus
});
// Use distributed lock
try {
const handle = await redlock.acquire();
// Critical section with distributed guarantee
await processPayment();
await redlock.release(handle);
} catch (error) {
console.error('Distributed lock failed:', error);
} finally {
// Disconnect all clients
await Promise.all(clients.map(client => client.disconnect()));
}
`$3
Both
ioredis.Cluster and node-redis cluster clients are fully supported:#### With ioredis.Cluster
`typescript
import { Cluster } from 'ioredis';
import { IoredisAdapter, createLock } from 'redlock-universal';const cluster = new Cluster([
{ host: 'redis-node-1', port: 6379 },
{ host: 'redis-node-2', port: 6379 },
{ host: 'redis-node-3', port: 6379 },
]);
// IoredisAdapter accepts both Redis and Cluster types
const adapter = new IoredisAdapter(cluster);
const lock = createLock({ adapter, key: 'resource', ttl: 30000 });
await lock.using(async (signal) => {
// Critical section with cluster HA
});
cluster.disconnect();
`#### With node-redis Cluster
`typescript
import { createCluster } from 'redis';
import { NodeRedisAdapter, createLock } from 'redlock-universal';const cluster = createCluster({
rootNodes: [
{ url: 'redis://redis-node-1:6379' },
{ url: 'redis://redis-node-2:6379' },
],
});
await cluster.connect();
const adapter = new NodeRedisAdapter(cluster);
const lock = createLock({ adapter, key: 'resource', ttl: 30000 });
`#### Understanding Cluster vs Redlock
> Important: Redis Cluster and the Redlock algorithm serve different purposes.
| Aspect | Single Redis Cluster | Multiple Independent Instances (Redlock) |
|--------|---------------------|------------------------------------------|
| Purpose | HA via replication + sharding | Distributed consensus across failure domains |
| Fault tolerance | Survives node failures within cluster | Survives arbitrary node/network failures |
| Consistency | Eventual (during failover) | Quorum-based consensus |
| Complexity | Simpler | More infrastructure |
| Best for | Most applications | Safety-critical systems |
Recommendation: For most applications, a single Redis Cluster provides sufficient high availability. For safety-critical systems where lock correctness is paramount, use multiple independent Redis instances with Redlock.
examples/redis-cluster-usage.ts for detailed examples.Core Concepts
$3
-
AUTO_EXTENSION_THRESHOLD_RATIO: 0.2 - Extension triggers at 80% TTL consumed
- ATOMIC_EXTENSION_SAFETY_BUFFER: 2000ms - Minimum TTL for safe extension
- MIN_EXTENSION_INTERVAL: 100ms - Prevents rapid retry loopsAPI Reference
$3
####
createLock(config)Creates a simple lock for single Redis instance.
`typescript
interface CreateLockConfig {
adapter: RedisAdapter;
key: string;
ttl?: number; // Default: 30000ms
retryAttempts?: number; // Default: 3
retryDelay?: number; // Default: 100ms
performance?: 'standard' | 'lean' | 'enterprise'; // Default: 'standard'
logger?: Logger; // See Logger Configuration
}
`#### Lock Methods
`typescript
// Acquire lock
const handle = await lock.acquire();// Release lock
const released = await lock.release(handle);
// Extend lock TTL
const extended = await lock.extend(handle, newTTL);
// Check if locked
const isLocked = await lock.isLocked(key);
// Auto-extending lock with routine execution (NEW!)
const result = await lock.using(async signal => {
// Your long-running operation here
// Lock automatically extends at 80% of TTL
// Check signal.aborted if extension fails
return 'operation-result';
});
// Advanced usage with abort signal checking
const result = await lock.using(async signal => {
for (let i = 0; i < 1000; i++) {
await processItem(i);
// Check for cancellation (e.g., if lock extension fails)
if (signal.aborted) {
console.log('Operation cancelled:', signal.error?.message);
break;
}
}
return { processed: i };
});
`#### Performance Modes
Choose the optimal performance mode for your use case:
`typescript
// Standard mode (default) - Full features with monitoring
const lock = createLock({
adapter: new NodeRedisAdapter(client),
key: 'resource',
performance: 'standard', // Full monitoring, health checks
});// Lean mode - Memory optimized for high-throughput scenarios
const leanLock = createLock({
adapter: new NodeRedisAdapter(client),
key: 'resource',
performance: 'lean', // Saves ~150KB memory, 3% faster
});
`Performance Mode Comparison:
- Standard: Full monitoring, health checks, comprehensive error details
- Lean: Memory-optimized, pre-allocated errors, minimal overhead
- Enterprise: Standard + circuit breakers + advanced observability (future)
#### Logger Integration
Configure structured logging for production observability:
`typescript
import { Logger, LogLevel } from 'redlock-universal';// Create logger instance
const logger = new Logger({
level: LogLevel.INFO,
prefix: 'redlock',
enableConsole: true, // Console output
enableCollection: true, // In-memory collection for metrics
maxEntries: 1000, // Limit memory usage
});
// Single-instance lock with logger
const lock = createLock({
adapter: new NodeRedisAdapter(client),
key: 'resource',
ttl: 30000,
logger, // Enhanced monitoring and error reporting
});
// Distributed lock with logger
const redlock = createRedlock({
adapters: [adapter1, adapter2, adapter3],
key: 'distributed-resource',
ttl: 30000,
logger, // Distributed lock state tracking
});
`Logger Configuration:
`typescript
interface LoggerConfig {
level: LogLevel; // DEBUG, INFO, WARN, ERROR
prefix?: string; // Log prefix for identification
enableConsole?: boolean; // Console output (default: true)
enableCollection?: boolean; // In-memory collection (default: false)
maxEntries?: number; // Max entries to keep (default: 100)
}
`What Gets Logged:
- β
Lock acquisition attempts and failures
- β
Circuit breaker state changes (open/closed/half-open)
- β
Redis connection health checks and recovery
- β
Auto-extension successes and failures
- β
Redis adapter warnings (disconnect issues)
- β
Lock release errors and cleanup issues
Accessing Collected Logs:
`typescript
// Get recent log entries for analysis
const entries = logger.getEntries();
console.log(Collected ${entries.length} log entries);// Check for errors in the last hour
const recentErrors = entries.filter(
entry =>
entry.level === LogLevel.ERROR && entry.timestamp > Date.now() - 3600000
);
`#### External Logger Integration
Use your existing production logger (Winston, Pino, Bunyan, etc.) instead of the
built-in Logger:
Winston (works directly):
`typescript
import winston from 'winston';
import { createLock } from 'redlock-universal';const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [new winston.transports.Console()],
});
const lock = createLock({
adapter,
key: 'resource',
logger, // Winston works directly - no adapter needed!
});
`Pino (requires adapter):
`typescript
import pino from 'pino';
import { createLock, createPinoAdapter } from 'redlock-universal';const pinoLogger = pino({ level: 'info' });
const logger = createPinoAdapter(pinoLogger); // Convert to ILogger
const lock = createLock({
adapter,
key: 'resource',
logger, // Pino adapter provides full compatibility
});
`Bunyan (requires adapter):
`typescript
import bunyan from 'bunyan';
import { createLock, createBunyanAdapter } from 'redlock-universal';const bunyanLogger = bunyan.createLogger({ name: 'myapp' });
const logger = createBunyanAdapter(bunyanLogger); // Convert to ILogger
const lock = createLock({
adapter,
key: 'resource',
logger, // Bunyan adapter provides full compatibility
});
`Compatible Loggers:
| Logger | Works Directly | Adapter Needed |
| --------------- | -------------- | ---------------------------- |
| Winston | β
Yes | No |
| Console | β
Yes | No |
| Built-in Logger | β
Yes | No |
| Bunyan | β οΈ Via Adapter |
createBunyanAdapter() |
| Pino | β οΈ Via Adapter | createPinoAdapter() |Note: Log4js uses
util.format() for all arguments, which stringifies context objects instead of preserving structured data. For structured logging, use Winston, Bunyan (with adapter), or Pino (with adapter).See
examples/ directory for complete integration examples.#### Lock Inspection
Inspect lock state atomically to debug stuck locks or monitor lock ownership:
`typescript
import { NodeRedisAdapter } from 'redlock-universal';const adapter = NodeRedisAdapter.from(client);
// Inspect lock state
const inspection = await adapter.inspect('my-resource');
if (inspection) {
console.log('Lock owner:', inspection.value);
console.log('TTL remaining:', inspection.ttl, 'ms');
// Use for debugging stuck locks
if (inspection.ttl < 1000) {
console.warn('Lock expiring soon!');
}
} else {
console.log('Lock is not currently held');
}
`Use Cases:
- Debugging: Identify which process holds a lock
- Monitoring: Track lock ownership and expiration
- Diagnostics: Understand lock contention issues
Important Notes:
- Returns
null if lock doesn't exist
- TTL is in milliseconds
- Operation is atomic (uses Lua script)
- Available on all adapters (NodeRedisAdapter, IoredisAdapter, MemoryAdapter)#### MemoryAdapter (Testing)
For unit tests without Redis:
`typescript
import { MemoryAdapter, createLock } from 'redlock-universal';// Create in-memory adapter
const adapter = new MemoryAdapter();
// Use in tests
const lock = createLock({
adapter,
key: 'test-resource',
ttl: 5000,
});
// Test lock behavior
const handle = await lock.acquire();
expect(handle.key).toBe('test-resource');
await lock.release(handle);
// Cleanup
adapter.clear(); // Remove all locks
await adapter.disconnect(); // Clean up timers
`β οΈ TESTING ONLY: MemoryAdapter is NOT suitable for production use. It lacks:
- Persistence (all locks lost on process restart)
- Cross-process synchronization (single process only)
- Network reliability (no Redis connection handling)
Features:
- Full RedisAdapter interface compatibility
- Proper TTL expiration (using setTimeout)
- Atomic batch operations
- No Docker or Redis dependencies
Test Setup Example:
`typescript
describe('My Feature', () => {
let adapter: MemoryAdapter; beforeEach(() => {
adapter = new MemoryAdapter();
});
afterEach(async () => {
adapter.clear(); // Clean locks between tests
await adapter.disconnect(); // Clean up timers
});
it('should handle lock contention', async () => {
const lock1 = createLock({ adapter, key: 'shared', ttl: 5000 });
const lock2 = createLock({ adapter, key: 'shared', ttl: 5000 });
await lock1.acquire();
await expect(lock2.acquire()).rejects.toThrow(); // Contention!
});
});
`$3
####
createRedlock(config)Creates a distributed lock using the Redlock algorithm.
`typescript
interface CreateRedlockConfig {
adapters: RedisAdapter[];
key: string;
ttl?: number; // Default: 30000ms
quorum?: number; // Default: majority
retryAttempts?: number; // Default: 3
retryDelay?: number; // Default: 200ms
clockDriftFactor?: number; // Default: 0.01
logger?: Logger; // See Logger Configuration
}
`$3
#### Node-Redis Adapter
`typescript
import { NodeRedisAdapter } from 'redlock-universal';
import { createClient } from 'redis';const client = createClient({ url: 'redis://localhost:6379' });
await client.connect();
// Basic adapter
const adapter = new NodeRedisAdapter(client);
// With logger support (NEW!)
const adapter = new NodeRedisAdapter(client, {
keyPrefix: 'myapp:', // Optional key prefix
timeout: 5000, // Redis operation timeout
logger: logger, // Structured logging for adapter operations
});
`#### Ioredis Adapter
`typescript
import { IoredisAdapter } from 'redlock-universal';
import Redis from 'ioredis';const client = new Redis('redis://localhost:6379');
// Basic adapter
const adapter = new IoredisAdapter(client);
// With logger support (NEW!)
const adapter = new IoredisAdapter(client, {
keyPrefix: 'myapp:', // Optional key prefix
timeout: 5000, // Redis operation timeout
maxRetries: 3, // Redis operation retries
retryDelay: 100, // Delay between retries
logger: logger, // Structured logging for adapter operations
});
`Redis Adapter Options:
`typescript
interface RedisAdapterOptions {
keyPrefix?: string; // Prefix for all Redis keys
maxRetries?: number; // Max retries for failed operations (default: 3)
retryDelay?: number; // Delay between retries in ms (default: 100)
timeout?: number; // Operation timeout in ms (default: 5000)
logger?: Logger; // See Logger Configuration
}
`What Adapters Log:
- β οΈ Redis disconnect warnings (connection cleanup issues)
- π Operation retries and timeouts
- π« Validation errors (invalid keys, TTL values)
- π Connection health and status changes
$3
Convenient functions for creating multiple locks or specialized configurations:
`typescript
import {
createLocks,
createPrefixedLock,
createRedlocks,
} from 'redlock-universal';// Create multiple locks with shared configuration
const locks = createLocks(adapter, ['user:123', 'account:456'], {
ttl: 15000,
retryAttempts: 5,
performance: 'lean',
});
// Create lock with automatic key prefixing
const userLock = createPrefixedLock(adapter, 'locks:user:', '123', {
ttl: 10000,
});
// Results in key: "locks:user:123"
// Create multiple distributed locks
const redlocks = createRedlocks(
[adapter1, adapter2, adapter3],
['resource1', 'resource2'],
{
ttl: 15000,
quorum: 2,
retryAttempts: 5,
}
);
`Advanced Usage
$3
`typescript
const lock = createLock({
adapter: new NodeRedisAdapter(client),
key: 'contested-resource',
ttl: 10000,
retryAttempts: 5, // Retry up to 5 times
retryDelay: 200, // Wait 200ms between retries
});
`$3
`typescript
const handle = await lock.acquire();// Extend lock by 10 more seconds
const extended = await lock.extend(handle, 10000);
if (extended) {
// Continue working with extended lock
await longRunningTask();
}
await lock.release(handle);
`$3
The
using() method provides automatic lock management with auto-extension for
long-running operations. It handles lock acquisition, automatic extension when
needed, and guaranteed cleanup.#### Simple Lock Auto-Extension
`typescript
// Auto-extending lock with routine execution
const result = await lock.using(async signal => {
// Long-running operation - lock automatically extends at 80% of TTL
await processLargeDataset(); // Check if extension failed (loss of lock)
if (signal.aborted) {
throw new Error(
Lock lost: ${signal.error?.message});
} return 'processing-complete';
});
console.log(result); // 'processing-complete'
`#### Distributed Lock Auto-Extension
`typescript
// Distributed lock with quorum-based auto-extension
const redlock = createRedlock({
adapters: [adapter1, adapter2, adapter3],
key: 'distributed-job',
ttl: 30000,
quorum: 2,
});const result = await redlock.using(async signal => {
for (const item of largeJobQueue) {
// Process each item - lock extends automatically
await processItem(item);
// Abort if quorum lost (majority of Redis nodes failed)
if (signal.aborted) {
throw new Error(
Distributed lock lost: ${signal.error?.message});
}
} return 'all-items-processed';
});
`#### Real-World Examples
For implementation patterns including database transactions, cache warming, and
job processing, see the examples directory.
$3
`typescript
import { LockAcquisitionError, LockReleaseError } from 'redlock-universal';try {
const handle = await lock.acquire();
// ... work ...
await lock.release(handle);
} catch (error) {
if (error instanceof LockAcquisitionError) {
console.error('Failed to acquire lock:', error.message);
} else if (error instanceof LockReleaseError) {
console.error('Failed to release lock:', error.message);
}
}
`$3
`typescript
// Lock multiple resources in consistent order (avoid deadlocks)
const userLock = createLock({ adapter, key: 'user:123' });
const accountLock = createLock({ adapter, key: 'account:456' });const userHandle = await userLock.acquire();
const accountHandle = await accountLock.acquire();
try {
// Perform transaction requiring both resources
await transferFunds();
} finally {
// Release in reverse order
await accountLock.release(accountHandle);
await userLock.release(userHandle);
}
`$3
Acquire multiple locks atomically with all-or-nothing semantics using
LockManager:`typescript
import { LockManager } from 'redlock-universal';const manager = new LockManager({
nodes: [adapter],
defaultTTL: 30000,
});
// Atomic batch acquisition - either all locks acquired or none
const handles = await manager.acquireBatch([
'user:123',
'account:456',
'order:789',
]);
try {
// All locks acquired atomically - perform multi-resource transaction
await processMultiResourceTransaction();
} finally {
// Release all locks
await manager.releaseBatch(handles);
}
`#### Batch with Auto-Extension
Combine batch acquisition with automatic lock renewal for long-running
operations:
`typescript
// Batch locks with auto-extension
await manager.usingBatch(
['user:123', 'account:456', 'order:789'],
async signal => {
// All locks acquired atomically and will auto-extend
for (const task of longRunningTasks) {
await processTask(task); // Check if any lock extension failed
if (signal.aborted) {
throw new Error('Lock extension failed - aborting operation');
}
}
return 'all-tasks-completed';
}
);
// All locks automatically released
`#### Atomicity Guarantee
Batch acquisition uses Redis Lua scripts to ensure atomicity:
- All-or-Nothing: Either all locks are acquired or the operation fails
- No Partial States: Prevents race conditions from acquiring locks
individually
- Deadlock Prevention: Keys are automatically sorted to ensure consistent
lock order
- Performance: Single Redis round-trip instead of N sequential acquisitions
`typescript
try {
const handles = await manager.acquireBatch([
'resource:1',
'resource:2',
'resource:3',
]);
// SUCCESS: All 3 locks acquired
} catch (error) {
if (error instanceof LockAcquisitionError) {
// FAILURE: None of the locks were acquired
console.error('Batch failed:', error.key, 'already locked');
}
}
`examples/batch-locks.ts.$3
Batch lock acquisition delivers significant performance improvements over
sequential locking:
Sequential vs Batch Comparison:
| Locks | Sequential | Batch | Speedup |
| ----- | ---------- | ------ | --------- |
| 3 | 2.34ms | 0.62ms | 3.8x |
| 5 | 3.46ms | 0.60ms | 5.8x |
| 10 | 4.98ms | 0.34ms | 14.7x |
_β Benchmarked on local Redis 7 (macOS, Node.js 22). **Performance varies between
runs** due to system load, network latency, and Redis configuration. The
relative speedup advantage (3-15x) remains consistent across different systems._
Key Performance Metrics:
- Throughput: 2,630 ops/sec for batch operations
- Auto-Extension Overhead: 0.0% (negligible impact)
- Scalability: Speedup increases with lock count
Why Batch is Faster:
- Single Lua script execution (atomic operation)
- Eliminates N network round-trips
- Sub-millisecond performance even for 10+ locks
- Automatic key sorting prevents deadlocks
`typescript
// Benchmark example: 10 locks
// Sequential: ~5ms (10 Redis calls)
// Batch: ~0.34ms (1 Lua script)
// Result: 14.7x faster β‘
`Best Practices
$3
`typescript
const handle = await lock.acquire();
try {
await doWork();
} finally {
await lock.release(handle);
}
`$3
`typescript
// Short-lived operations
const lock = createLock({ adapter, key: 'quick-task', ttl: 5000 });// Long-running operations
const lock = createLock({ adapter, key: 'batch-job', ttl: 300000 });
`$3
`typescript
const lock = createLock({
adapter,
key: 'popular-resource',
retryAttempts: 3,
retryDelay: 100,
});try {
const handle = await lock.acquire();
// ... work ...
} catch (error) {
if (error instanceof LockAcquisitionError) {
// Resource is busy, handle gracefully
await scheduleForLater();
}
}
`$3
`typescript
// For 5 Redis instances, use quorum of 3
const redlock = createRedlock({
adapters: [redis1, redis2, redis3, redis4, redis5],
quorum: 3, // Majority consensus
key: 'critical-resource',
});
`Monitoring and Observability
$3
`typescript
// Access lock metadata
const handle = await lock.acquire();
console.log('Lock acquired in:', handle.metadata.acquisitionTime, 'ms');
console.log('Attempts required:', handle.metadata.attempts);// For distributed locks
const redlockHandle = await redlock.acquire();
console.log('Nodes locked:', redlockHandle.metadata.nodes.length);
console.log('Quorum achieved:', redlockHandle.metadata.nodes.length >= quorum);
`$3
`typescript
import { Logger, LogLevel } from 'redlock-universal';// Production logging setup
const logger = new Logger({
level: LogLevel.INFO,
prefix: 'redlock',
enableConsole: true, // For development
enableCollection: true, // For metrics collection
maxEntries: 1000, // Memory limit
});
// Configure locks with logger
const lock = createLock({ adapter, key: 'resource', logger });
// Monitor lock operations
const entries = logger.getEntries();
const errors = entries.filter(e => e.level === LogLevel.ERROR);
const warnings = entries.filter(e => e.level === LogLevel.WARN);
console.log(
Lock errors: ${errors.length}, Warnings: ${warnings.length});
`$3
- β
Race condition protection: Atomic extension scripts eliminate timing
race conditions in auto-extension
- β
Consistent logging: All components use structured Logger instead of
mixed console.\* calls
- β
Zero NODE_ENV checks: Production code no longer depends on environment
variables for behavior
- β
Configurable observability: Enable/disable console output and metrics
collection independently
- β
Enhanced context: All log entries include relevant context (keys,
correlation IDs, timestamps)
- β
Memory management: Built-in log rotation with configurable limits
- β
TTL feedback: Atomic operations provide real-time TTL information for
intelligent scheduling
Performance
redlock-universal delivers industry-leading performance:
- Lock acquisition: 0.48ms mean latency (P95: 0.75ms) in lean mode
- Memory usage: <2KB per operation (60% reduction via buffer pooling)
- Throughput: 3,329 ops/sec (42% faster than redis-semaphore, 95% faster
than node-redlock)
- Batch operations: 3.8x - 14.7x faster than sequential (scales with lock
count)
- Test coverage: 86%+ with 487 unit, integration, and E2E tests
Performance modes:
- Standard (default): Full monitoring and observability features
- Lean: Memory-optimized with minimal overhead for maximum speed
- Enterprise: Additional health checks and circuit breakers
Recent Optimizations (v0.6.5):
- Buffer pooling reduces GC pressure by 60%
- Fast-path optimizations for circuit breaker checks
- Zero-allocation logging in production mode
- 51% faster lean mode vs standard mode
$3
We provide benchmarks to validate performance claims:
`bash
Compare with leading Redis lock libraries
npm run benchmark:competitiveInternal performance validation
npm run benchmark:performanceRun all benchmarks
npm run benchmark
`Benchmark Philosophy: We believe in honest, reproducible performance
testing. Our benchmarks:
- Test against real Redis instances (not mocks)
- Include statistical analysis (mean, p50, p95, p99)
- Acknowledge performance variability between runs
- Focus on competitive positioning rather than absolute claims
Comparison with Alternatives
> Methodology: This comparison uses data from npm registry (July 2025) and
> architectural analysis. Performance estimates are based on implementation
> patterns and Redis operation complexity.
$3
| Feature | redlock-universal | node-redlock | redis-semaphore |
| ----------------------------------- | ----------------- | ------------ | ----------------- |
| Client Support |
| node-redis v4+ | β
Native | β | β οΈ Wrapper needed |
| ioredis v5+ | β
Native | β
Required | β
Native |
| Language & Developer Experience |
| TypeScript | β
First-class | β
Native | β
Native |
| Modern ESM | β
| β οΈ CJS focus | β
|
| API Design | β
Intuitive | β οΈ Complex | β
Clean |
| Error Types | β
Specific | β
Basic | β
Detailed |
| Locking Capabilities |
| Single Instance | β
Optimized | β | β
|
| Distributed (Redlock) | β
Full spec | β
Full spec | β
RedlockMutex |
| Lock Extension | β
Manual/Auto | β
Watchdog | β
Auto-refresh |
| Semaphores | β Planned | β | β
Advanced |
| Production Features |
| Retry Logic | β
Configurable | β
Built-in | β
Fair queue |
| Monitoring | β
Built-in | β | β |
| Health Checks | β
Built-in | β | β |
| Structured Logging | β
Built-in | β | β |
$3
| Metric | redlock-universal | node-redlock | redis-semaphore |
| ------------------------------- | ------------------------ | ------------- | --------------- |
| Maintenance & Adoption |
| Weekly Downloads | _New Package_ | 644,599 | 282,020 |
| Last Updated | 2025 Active | Mar 2022 β οΈ | Mar 2025 β
|
| Maintenance Status | β
Active | β οΈ Stale (3y) | β
Active |
| Package Quality |
| Runtime Dependencies | 0 (peer only) | 1 | 1 |
| TypeScript Support | β
Native | β
Native | β
Native |
| Test Coverage | 85%+ Unit + Integration | Unknown | Unknown |
| Performance Characteristics |
| Lock Acquisitionβ | 0.48ms (P95: 0.75ms) | ~0.4-0.8ms | ~0.4-0.6ms |
| Throughput (ops/sec)β | 3,329 | 1,702 | 2,340 |
| Memory per Operationβ | <2KB | ~8KB | ~6KB |
_\Benchmarked on local Redis 7 (macOS, Node.js 22). *Performance varies
between runs** due to system load, network latency, and Redis configuration. All
tested libraries deliver competitive sub-millisecond performance. Focus on
features and reliability over micro-optimizations._
$3
| Package | Status | Assessment |
| ------------------- | ----------------------- | ------------------------------------------------ |
| node-redlock | Last updated March 2022 | Consider compatibility with newer Redis versions |
| redis-semaphore | Actively maintained | Good feature set, reliable choice |
$3
#### β
Universal Compatibility
- Only library supporting both node-redis v4+ and ioredis v5+ natively
- Future-proof: Works with latest Redis client versions
- Migration-friendly: Easy to switch between Redis clients
#### β
Production-Ready Observability
- Built-in metrics: Track lock performance, acquisition times, success rates
- Health monitoring: Redis connection health checks and statistics
- Structured logging: Configurable logging with context and levels
- Zero competitors offer these enterprise features
#### β
Modern Architecture & DX
- TypeScript-first: Strict typing, excellent IntelliSense
- ESM native: Modern module system with CommonJS compatibility
- Zero runtime dependencies: Security and supply chain safety
- Code quality: 85%+ test coverage with unit and integration tests
#### β
Proven Algorithm Implementation
- Redis-spec compliant: Follows official Redlock specification
- Clock drift handling: Proper time synchronization assumptions
- Fault tolerance: Graceful degradation on partial failures
- Performance optimized: Memory-efficient buffer pooling, sub-millisecond
acquisition, and highest throughput among tested libraries (verified
benchmarks included)
$3
#### From node-redlock
`typescript
// Before (node-redlock) - Stale for 3 years
const redlock = new Redlock([redis1, redis2], { retryCount: 3 });
const resource = await redlock.acquire(['resource'], 30000);
await redlock.release(resource);// After (redlock-universal) - Modern & maintained
const redlock = createRedlock({
adapters: [new IoredisAdapter(redis1), new IoredisAdapter(redis2)],
key: 'resource',
ttl: 30000,
retryAttempts: 3,
});
const handle = await redlock.acquire();
await redlock.release(handle);
`#### From redis-semaphore
`typescript
// Before (redis-semaphore) - Good but limited to ioredis
const mutex = new Mutex(redis, 'resource', { acquireTimeout: 30000 });
const release = await mutex.acquire();
release();// After (redlock-universal) - Universal client support + monitoring
const lock = createLock({
adapter: new NodeRedisAdapter(nodeRedisClient), // or IoredisAdapter
key: 'resource',
ttl: 30000,
});
const handle = await lock.acquire();
await lock.release(handle);
`Testing
`bash
Run unit tests
npm testRun integration tests (requires Redis)
npm run test:integrationRun all tests with coverage
npm run test:coverageRun Docker-based tests
npm run test:docker
`FAQ
Q: What's the performance overhead of auto-extension? A: Minimal - typically
<1ms using atomic operations.
Q: How does this handle Redis restarts? A: Lua scripts auto-reload on
NOSCRIPT errors, no action needed.
Q: SimpleLock vs RedLock? A: SimpleLock = single Redis (faster). RedLock =
multiple Redis (fault-tolerant).
Troubleshooting
$3
Lock not releasing:
- Ensure the lock handle matches the stored value
- Check if TTL expired before release attempt
- Verify Redis connectivity
Auto-extension not working:
- Verify ATOMIC_EXTENSION_SAFETY_BUFFER is defined (2000ms default)
- Check that TTL is long enough for your operation
- Monitor the AbortSignal for extension failures
Circuit breaker opening frequently:
- Increase timeout values
- Check Redis server performance
- Review network latency
"NOSCRIPT" errors:
- Redis flushed Lua script cache
- Library automatically reloads scripts
- No action needed, but indicates Redis restart
Connection timeouts:
- Check Redis maxclients setting
- Review connection pool configuration
- Monitor network latency between app and Redis
Examples
Quick examples are shown above. For detailed implementations:
Real-World Patterns:
- Database Transactions - Transaction
safety patterns
- Distributed Cache Warming - Distributed cache
coordination
- Job Processing with Progress - Long-running
job management
Core Usage:
- Simple Lock Usage - Basic locking patterns
- Distributed Lock (RedLock) - Multi-instance
coordination
- Lock Extension Patterns - Manual extension
strategies
- Retry Strategies - Contention handling
- Monitoring & Observability - Production monitoring
- Adapter Usage - Redis client integration
Contributing
We welcome contributions! Please see our Contributing Guide
for details.
1. Fork the repository
2. Create your feature branch (
git checkout -b feature/amazing-feature)
3. Commit your changes (git commit -m 'Add amazing feature')
4. Push to the branch (git push origin feature/amazing-feature`)MIT Β© Alex Potapenko
- π Documentation
- π Issue Tracker
- π¬ Discussions
---
Made with β€οΈ for the Node.js community