
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
redlock-universal
Advanced tools
Production-ready distributed locks for Redis and Valkey with support for node-redis, ioredis, and Valkey GLIDE
Universal distributed locks for Redis and Valkey
The only distributed lock library supporting all three Redis clients:
node-redis • ioredis • Valkey GLIDE
NestJS Integration: Check out nestjs-redlock-universal for decorator-based integration with dependency injection.
import { createLock, IoredisAdapter } from 'redlock-universal';
import Redis from 'ioredis';
const lock = createLock({
adapter: new IoredisAdapter(new Redis()),
key: 'my-resource',
ttl: 30000,
});
// Automatic lock management with auto-extension
await lock.using(async () => {
// Critical section - lock auto-extends and releases
});
npm install redlock-universal
Same performance as the fastest libraries, with universal client support they lack.
| Library | Latency | node-redis | ioredis | Valkey | Monitoring |
|---|---|---|---|---|---|
| redis-semaphore | 0.369ms | ❌ | ✅ | ❌ | ❌ |
| redlock-universal | 0.377ms | ✅ | ✅ | ✅ | ✅ |
| node-redlock | 0.398ms | ❌ | ✅ | ❌ | ❌ |
Benchmarked on local Redis 7 (macOS, Node.js 22). Results vary with system load, network, and Redis configuration. All libraries deliver competitive sub-millisecond performance.
npm install redlock-universal
# Install your preferred Redis client
npm install ioredis # or
npm install redis # or
npm install @valkey/valkey-glide
Auto-extends lock and guarantees cleanup:
const result = await lock.using(async (signal) => {
// Long-running operation
await processData();
// Check if lock was lost (optional)
if (signal.aborted) {
throw new Error('Lock lost');
}
return 'done';
});
const handle = await lock.acquire();
try {
await doWork();
} finally {
await lock.release(handle);
}
const handle = await lock.acquire();
// Extend lock by 10 seconds
const extended = await lock.extend(handle, 10000);
if (extended) {
// Continue working with extended TTL
}
await lock.release(handle);
When using LockManager, you can extend locks by creating a lock instance from the handle:
const manager = new LockManager({ nodes: [adapter], defaultTTL: 30000 });
const handle = await manager.acquireLock('my-resource');
// Create a lock instance using the handle's strategy
const lock = handle.metadata?.strategy === 'redlock'
? manager.createRedLock(handle.key)
: manager.createSimpleLock(handle.key);
const extended = await lock.extend(handle, 30000);
This also works across processes — LockHandle is fully serializable:
// Process A: acquire and pass handle to another process
const handle = await manager.acquireLock('my-resource');
queue.publish(JSON.stringify(handle));
// Process B: receive handle and extend
const handle = JSON.parse(message);
const lock = handle.metadata?.strategy === 'redlock'
? manager.createRedLock(handle.key)
: manager.createSimpleLock(handle.key);
await lock.extend(handle, 30000);
createSimpleLock and createRedLock don't acquire anything — they create a lock object wired to the right adapter. The actual Redis operation only happens when you call .extend().
const locked = await lock.isLocked('my-resource');
if (!locked) {
// Safe to acquire
}
For fault-tolerant locking across multiple Redis instances using the Redlock algorithm:
import { createRedlock, IoredisAdapter } from 'redlock-universal';
const redlock = createRedlock({
adapters: [
new IoredisAdapter(redis1),
new IoredisAdapter(redis2),
new IoredisAdapter(redis3),
],
key: 'distributed-resource',
ttl: 30000,
quorum: 2,
});
await redlock.using(async () => {
// Distributed consensus - survives node failures
});
Fully supports Redis Cluster via both ioredis and node-redis.
// ioredis
import Redis from 'ioredis';
const adapter = new IoredisAdapter(new Redis());
// ioredis Cluster
import { Cluster } from 'ioredis';
const cluster = new Cluster([{ host: 'node-1', port: 6379 }]);
const adapter = new IoredisAdapter(cluster);
// node-redis
import { createClient } from 'redis';
const client = createClient();
await client.connect();
const adapter = new NodeRedisAdapter(client);
// node-redis Cluster
import { createCluster } from 'redis';
const cluster = createCluster({ rootNodes: [{ url: 'redis://node-1:6379' }] });
await cluster.connect();
const adapter = new NodeRedisAdapter(cluster);
// Valkey GLIDE
import { GlideClient } from '@valkey/valkey-glide';
const client = await GlideClient.createClient({ addresses: [{ host: 'localhost', port: 6379 }] });
const adapter = new GlideAdapter(client);
Valkey Users: See VALKEY.md for detailed Valkey setup guide.
[!IMPORTANT] Cluster vs Redlock:
- Redis Cluster: Provides High Availability (HA). If a master fails, a replica takes over. Warning: Locks can be lost during failover (eventual consistency).
- Redlock: Provides Consensus. Locks are safe even if nodes crash. Use for critical consistency.
See Cluster Usage Examples for details.
interface CreateLockConfig {
adapter: RedisAdapter;
key: string;
ttl?: number; // Default: 30000ms
retryAttempts?: number; // Default: 3
retryDelay?: number; // Default: 100ms
performance?: 'standard' | 'lean' | 'enterprise';
logger?: ILogger; // Optional structured logging
circuitBreaker?: boolean | CircuitBreakerConfig; // Default: enabled
}
interface CreateRedlockConfig {
adapters: RedisAdapter[];
key: string;
ttl?: number; // Default: 30000ms
retryAttempts?: number; // Default: 3
retryDelay?: number; // Default: 200ms
quorum?: number; // Default: Math.floor(adapters.length / 2) + 1
clockDriftFactor?: number; // Default: 0.01
logger?: ILogger; // Optional structured logging
}
Acquire multiple locks atomically (all-or-nothing):
import { LockManager } from 'redlock-universal';
const manager = new LockManager({ nodes: [adapter] });
// Atomic batch - prevents deadlocks via automatic key sorting
const handles = await manager.acquireBatch(['user:1', 'user:2', 'order:3']);
try {
await processTransaction();
} finally {
await manager.releaseBatch(handles);
}
// Or with auto-extension (supports retryAttempts, retryDelay options)
await manager.usingBatch(['key1', 'key2'], async (signal) => {
// All locks auto-extend and release
});
import { Logger, LogLevel } from 'redlock-universal';
const logger = new Logger({
level: LogLevel.INFO,
prefix: 'redlock',
enableConsole: true,
});
const lock = createLock({ adapter, key: 'resource', logger });
External loggers:
| Logger | Works Directly | Adapter Needed |
|---|---|---|
| Winston | Yes | No |
| Console | Yes | No |
| Pino | Via Adapter | createPinoAdapter() |
| Bunyan | Via Adapter | createBunyanAdapter() |
// Pino
import { createPinoAdapter } from 'redlock-universal';
const logger = createPinoAdapter(pinoLogger);
// Bunyan
import { createBunyanAdapter } from 'redlock-universal';
const logger = createBunyanAdapter(bunyanLogger);
Debug stuck locks:
const inspection = await adapter.inspect('my-resource');
if (inspection) {
console.log('Owner:', inspection.value);
console.log('TTL:', inspection.ttl, 'ms');
}
Unit tests without Redis:
import { MemoryAdapter, createLock } from 'redlock-universal';
const adapter = new MemoryAdapter();
const lock = createLock({ adapter, key: 'test', ttl: 5000 });
// Use in tests
const handle = await lock.acquire();
await lock.release(handle);
// Cleanup
adapter.clear();
await adapter.disconnect();
[!WARNING] MemoryAdapter is for testing only. Not suitable for production.
Create multiple locks or specialized configurations:
import { createLocks, createPrefixedLock, createRedlocks } from 'redlock-universal';
// Multiple locks with shared config
const locks = createLocks(adapter, ['user:123', 'account:456'], {
ttl: 15000,
retryAttempts: 5,
});
// Lock with automatic key prefixing
const userLock = createPrefixedLock(adapter, 'locks:user:', '123', {
ttl: 10000,
});
// Key: "locks:user:123"
// Multiple distributed locks
const redlocks = createRedlocks(
[adapter1, adapter2, adapter3],
['resource1', 'resource2'],
{ ttl: 15000, quorum: 2 }
);
Choose the optimal mode for your use case:
// Standard (default) - full monitoring and observability
const lock = createLock({ adapter, key: 'resource', performance: 'standard' });
// Lean - memory-optimized, minimal overhead (~3% faster)
const lock = createLock({ adapter, key: 'resource', performance: 'lean' });
// Enterprise - standard with advanced observability
const lock = createLock({ adapter, key: 'resource', performance: 'enterprise' });
SimpleLock includes a built-in circuit breaker that fast-fails when Redis is persistently unavailable, avoiding long TCP timeouts on every acquire() call.
States: Closed (normal) → Open (fast-fail) → Half-Open (single probe) → Closed
// Enabled by default — customize thresholds:
const lock = createLock({
adapter, key: 'resource',
circuitBreaker: {
failureThreshold: 5, // Consecutive failures to trip (default: 5)
resetTimeout: 60000, // Ms before probing recovery (default: 60000)
healthCheckInterval: 30000 // Ms between health checks (default: 30000)
}
});
// Disable entirely:
const lock = createLock({ adapter, key: 'resource', circuitBreaker: false });
// Check breaker state:
const health = lock.getHealth();
// { healthy, lastCheck, connected, circuitBreaker: { state, failures, openedAt } }
The circuit breaker is available in standard and enterprise performance modes. The lean mode omits it for minimal memory overhead.
import {
LockAcquisitionError,
LockReleaseError,
LockExtensionError,
} from 'redlock-universal';
try {
const handle = await lock.acquire();
await lock.extend(handle, 10000);
await lock.release(handle);
} catch (error) {
if (error instanceof LockAcquisitionError) {
// Lock is held by another process
} else if (error instanceof LockExtensionError) {
// Extension failed (lock expired or lost)
} else if (error instanceof LockReleaseError) {
// Release failed (handle mismatch or connection issue)
}
}
Q: SimpleLock vs RedLock? SimpleLock = single Redis (faster). RedLock = multiple Redis instances (fault-tolerant).
Q: What happens if Redis restarts? Lua scripts auto-reload on NOSCRIPT errors. No action needed.
Q: Performance overhead of auto-extension? Minimal (<1ms). Uses atomic Lua scripts.
using() over manual acquire/release - guarantees cleanup, handles auto-extensionsignal.aborted - gracefully exit when lock is lost during long operationsuser:123:cart)npm test # Unit tests
npm run test:integration # Integration tests (requires Redis)
npm run test:coverage # Coverage report
npm run test:docker # Docker-based tests (all services)
[!WARNING] Lock not releasing? Ensure handle matches stored value. Check if TTL expired before release.
[!WARNING] High P99 latency? Check Redis server load. Consider
performance: 'lean'mode.
// Before (node-redlock)
const redlock = new Redlock([ioredis], { retryCount: 3 });
const lock = await redlock.acquire(['resource'], 30000);
await lock.release();
// After (redlock-universal)
const redlock = createRedlock({
adapters: [new IoredisAdapter(ioredis)],
key: 'resource',
ttl: 30000,
retryAttempts: 3,
});
const handle = await redlock.acquire();
await redlock.release(handle);
// Before (redis-semaphore)
const mutex = new Mutex(ioredis, 'resource');
await mutex.acquire();
await mutex.release();
// After (redlock-universal)
const lock = createLock({
adapter: new IoredisAdapter(ioredis),
key: 'resource',
});
const handle = await lock.acquire();
await lock.release(handle);
See CONTRIBUTING.md. Issues and PRs welcome.
FAQs
Production-ready distributed locks for Redis and Valkey with support for node-redis, ioredis, and Valkey GLIDE
The npm package redlock-universal receives a total of 32,049 weekly downloads. As such, redlock-universal popularity was classified as popular.
We found that redlock-universal demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.