Skip to content

Locks Plugin

Distributed locking solution for coordinating access to shared resources across multiple application instances.

Overview

The Locks Plugin provides Redis-based distributed locking with automatic renewal, ownership validation, and configurable retry strategies. Useful for preventing race conditions in distributed systems.

ChallengeWithout LocksWith Locks Plugin
Double PaymentMultiple instances process same paymentSingle instance processes, others skip
Race ConditionsConcurrent updates corrupt dataSequential, atomic updates
Job DuplicationMultiple workers execute same jobOne worker per job
Inventory Oversell10 items available, 15 soldInventory tracking

Key Features

  • Redis-Based Locking — Atomic lock acquisition using Lua scripts
  • Automatic Renewal — Configurable TTL extension for long-running operations
  • Ownership Validation — Unique tokens prevent accidental release by other processes
  • Retry Strategies — Exponential backoff with configurable multiplier and delay caps
  • Graceful Shutdown — Automatic lock release on application termination
  • Decorator API@WithLock decorator for declarative locking

Installation

bash
npm install @nestjs-redisx/core @nestjs-redisx/locks ioredis
bash
npm install @nestjs-redisx/core @nestjs-redisx/locks redis

Basic Configuration

typescript
import { Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-redisx/core';
import { LocksPlugin } from '@nestjs-redisx/locks';

@Module({
  imports: [
    RedisModule.forRoot({
      clients: {
        host: 'localhost',
        port: 6379,
      },
      plugins: [
        new LocksPlugin({
          defaultTtl: 30000,
          keyPrefix: '_lock:',
          autoRenew: {
            enabled: true,
            intervalFraction: 0.5,
          },
        }),
      ],
    }),
  ],
})
export class AppModule {}

Usage with Decorator

typescript
import { Injectable } from '@nestjs/common';
import { WithLock } from '@nestjs-redisx/locks';
import { Payment, PaymentAlreadyProcessedError, OrderRepository, PaymentGateway } from './types';

@Injectable()
export class PaymentService {
  constructor(
    private readonly orderRepository: OrderRepository,
    private readonly paymentGateway: PaymentGateway,
  ) {}

  @WithLock({
    key: 'payment:{0}',
    ttl: 10000,
  })
  async processPayment(orderId: string): Promise<Payment> {
    // Only one instance can process this order at a time
    const order = await this.orderRepository.findById(orderId);

    if (order.status === 'paid') {
      throw new PaymentAlreadyProcessedError(orderId);
    }

    const result = await this.paymentGateway.charge(order);
    await this.orderRepository.update(orderId, { status: 'paid' });

    return result;
  }
}

Usage with Service API

typescript
import { Injectable, Inject } from '@nestjs/common';
import { LOCK_SERVICE, ILockService } from '@nestjs-redisx/locks';
import { InventoryStore } from './types';

@Injectable()
export class InventoryService {
  constructor(
    @Inject(LOCK_SERVICE) private readonly lockService: ILockService,
    private readonly inventory: InventoryStore,
  ) {}

  async reserveItem(itemId: string, quantity: number): Promise<boolean> {
    return this.lockService.withLock(
      `inventory:${itemId}`,
      async () => {
        const stock = await this.inventory.getStock(itemId);

        if (stock < quantity) {
          return false;
        }

        await this.inventory.decrement(itemId, quantity);
        return true;
      },
      { ttl: 5000 },
    );
  }
}

Lock Flow

Common Use Cases

ScenarioImplementation
Payment ProcessingLock per order ID to prevent double charges
Inventory ManagementLock per SKU during stock updates
Job ProcessingLock per job ID for at-most-once execution
Leader ElectionSingle lock for scheduler or cron jobs
Sequential OperationsLock per resource for ordered operations

Documentation

TopicDescription
Core ConceptsUnderstanding distributed locking
ConfigurationConfiguration reference
@WithLock DecoratorDeclarative locking
Service APIProgrammatic lock operations
Auto-RenewalTTL extension for long operations
Retry StrategiesHandling lock contention
PatternsCommon locking patterns
MonitoringMetrics and observability
TestingTesting locked services
RecipesImplementation examples
TroubleshootingDebugging common issues

Released under the MIT License.