New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

redlock-universal

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

redlock-universal

Production-ready distributed locks for Redis and Valkey with support for node-redis, ioredis, and Valkey GLIDE

latest
Source
npmnpm
Version
0.8.4
Version published
Weekly downloads
34K
-1.79%
Maintainers
1
Weekly downloads
 
Created
Source

redlock-universal

Universal distributed locks for Redis and Valkey

npm version Node.js TypeScript Test Coverage License: MIT Downloads

The only distributed lock library supporting all three Redis clients:

node-redisioredisValkey GLIDE

NestJS NestJS Integration: Check out nestjs-redlock-universal for decorator-based integration with dependency injection.

Quick Start

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

Why redlock-universal?

Same performance as the fastest libraries, with universal client support they lack.

LibraryLatencynode-redisioredisValkeyMonitoring
redis-semaphore0.369ms
redlock-universal0.377ms
node-redlock0.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.

Installation

npm install redlock-universal

# Install your preferred Redis client
npm install ioredis        # or
npm install redis          # or
npm install @valkey/valkey-glide

Core API

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';
});

acquire() / release() - Manual Control

const handle = await lock.acquire();
try {
  await doWork();
} finally {
  await lock.release(handle);
}

extend() - Extend Lock TTL

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);
Extending locks acquired via LockManager

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().

isLocked() - Check Lock Status

const locked = await lock.isLocked('my-resource');
if (!locked) {
  // Safe to acquire
}

Distributed Lock (Redlock Algorithm)

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
});

Adapters & Cluster Support

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.

Configuration

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
}
Advanced: Batch Lock Acquisition

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
});
Advanced: Logger Integration
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:

LoggerWorks DirectlyAdapter Needed
WinstonYesNo
ConsoleYesNo
PinoVia AdaptercreatePinoAdapter()
BunyanVia AdaptercreateBunyanAdapter()
// Pino
import { createPinoAdapter } from 'redlock-universal';
const logger = createPinoAdapter(pinoLogger);

// Bunyan
import { createBunyanAdapter } from 'redlock-universal';
const logger = createBunyanAdapter(bunyanLogger);
Advanced: Lock Inspection

Debug stuck locks:

const inspection = await adapter.inspect('my-resource');
if (inspection) {
  console.log('Owner:', inspection.value);
  console.log('TTL:', inspection.ttl, 'ms');
}
Advanced: Testing with MemoryAdapter

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.

Advanced: Factory Functions

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 }
);
Advanced: Performance Modes

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' });
Advanced: Circuit Breaker

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.

Error Handling

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)
  }
}

FAQ

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.

Best Practices

  • Use using() over manual acquire/release - guarantees cleanup, handles auto-extension
  • Set appropriate TTL - long enough for work, short enough for quick recovery
  • Handle signal.aborted - gracefully exit when lock is lost during long operations
  • Use unique lock keys - namespace by resource type (e.g., user:123:cart)
  • Monitor lock metrics - track acquisition failures and extension patterns

Testing

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)

Troubleshooting

[!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.

Migration

From node-redlock
// 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);
From redis-semaphore
// 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);

Contributing

See CONTRIBUTING.md. Issues and PRs welcome.

License

MIT

Keywords

redis

FAQs

Package last updated on 13 Mar 2026

Did you know?

Socket

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.

Install

Related posts