A node.js locks library with support of Redis and MongoDB

Overview

Buuild and Test Coverage Badge

locco

A small and simple library to deal with race conditions in distributed systems by applying locks on resources. Currently, supports locking via Redis, MongoDB, and in-memory object.

Installation

npm i @kontsedal/locco

Core logic

With locks, user can just say "I'm doing some stuff with this user, please lock him and don't allow anybody to change him" and no one will, till a lock is valid.

The core logic is simple. When we create a lock we generate a unique string identifying a current lock operation. Then, we search for a valid lock with a same key in the storage(Redis, Mongo, js object) and if it doesn't exist we add one and proceed. If a valid lock already exists we retry this operation for some time and then fail.

When we release or extend a lock, we check that lock exists in the storage and has the same unique identifier with a current lock. It makes impossible to release or extend other process lock.

Usage

There are two ways to create a resource lock. In the first one, you should manually lock and unlock a resource. Here is an example with a Redis:

import { Locker, IoRedisAdapter } from "@kontsedal/locco";
import Redis from "ioredis";

const redisAdapter = new IoRedisAdapter({ client: new Redis() });
const locker = new Locker({
  adapter: redisAdapter,
  retrySettings: { retryDelay: 200, retryTimes: 10 },
});

const lock = await locker.lock("user:123", 3000).aquire();
try {
  //do some risky stuff here
  //...
  //
  await lock.extend(2000);
  //do more risky stuff
  //...
} catch (error) {
} finally {
  await lock.release();
}

In the second one, you pass a function in the acquire method and a lock will be released automatically when a function finishes. Here is an example with a mongo:

import { Locker, IoRedisAdapter, MongoAdapter } from "@kontsedal/Locker";
import { MongoClient } from "mongodb";

const mongoAdapter = new MongoAdapter({
  client: new MongoClient(process.env.MONGO_URL),
});
const locker = new Locker({
  adapter: mongoAdapter,
  retrySettings: { retryDelay: 200, retryTimes: 10 },
});

await locker.lock("user:123", 3000).setRetrySettings({retryDelay: 200, retryTimes: 50}).aquire(async (lock) => {
  //do some risky stuff here
  //...
  await lock.extend(2000);
  //do some risky stuff here
  //...
});

API

Locker

The main class is responsible for the creation of new locks and passing them a storage adapter and default retrySettings.

Constructor params:

parameter type isRequired description
params.adapter ILockAdapter true Adapter to work with a lock keys storage. Currently Redis, Mongo and in-memory adapters are implemented
params.retrySettings object true
params.retrySettings.retryTimes number(milliseconds) false How many times we should retry lock before fail
params.retrySettings.retryDelay number(milliseconds) false How much time should pass between retries
params.retrySettings.totalTime number(milliseconds) false How much time should all retries last in total
params.retrySettings.retryDelayFn function false Function which returns a retryDelay for each attempt. Allows to implement an own delay logic

Example of a retryDelayFn usage:

const locker = new Locker({
  adapter: new InMemoryAdapter(),
  retrySettings: {
    retryDelayFn: ({
      attemptNumber, // starts from 0
      startedAt, // date of start in milliseconds
      previousDelay,
      settings, // retrySettings
      stop, // function to stop a retries, throws an error
    }) => {
      if (attemptNumber === 4) {
        stop();
      }
      return (attemptNumber + 1) * 50;
    },
  },
});

Provided example will do the same as providing retryTimes = 5, retryDelay = 50

Methods

lock(key: string, ttl: number) => Lock

Creates a Lock instance with provided key and time to live in milliseconds. It won't lock a resource at this point. Need to call an aquire() to do so

Lock.aquire(cb?: (lock: Lock) => void) => Promise<Lock>

Locks a resource if possible. If not, it retries as much as specified in the retrySettings. If callback is provided, lock will be released after a callback execution.

Lock.release({ throwOnFail?: boolean }) => Promise<void>

Unlocks a resource. If a resource is invalid (already taken by other lock or expired) it won't throw an error. To make it throw an error, need to provide {throwOnFail:true}.

Lock.extend(ttl: number) => Promise<void>

Extends a lock for a provided milliseconds from now. Will throw an error if current lock is already invalid

Lock.isLocked() => Promise<boolean>

Checks if a lock is still valid

Lock.setRetrySettings(settings: RetrySettings) => Promise<Lock>

Overrides a default retry settings of the lock.


Redis adapter

Requires only a compatible with ioredis client:

import { IoRedisAdapter } from "@kontsedal/locco";
import Redis from "ioredis";

const redisAdapter = new IoRedisAdapter({ client: new Redis() });

How it works

It relies on a Redis SET command with options NX and PX.

NX - ensures that a record will be removed after provided time

PX - ensures that if a record already exists it won't be replaced with a new one

So, to create a lock we just execute a SET command and if it returns "OK" response means that lock is created, if it returns null - a resource is locked.

To release or extend a lock, firstly, it gets a current key value(which is a unique string for each lock) and compares it with a current one. If it matches we either remove the key or set a new TTL for it.


Mongo adapter

Requires a mongo client and optional database name and lock collection name:

import { MongoAdapter } from "@kontsedal/locco";
import { MongoClient } from "mongodb";

const mongoAdapter = new MongoAdapter({
  client: new MongoClient(process.env.MONGO_URL),
  dbName: "my-db", // optional parameter
  locksCollectionName: "locks", //optional parameter, defaults to "locco-locks"
});

How it works

We create a collection of locks in the database with the next fields:

  • key: string
  • uniqueValue: string
  • expireAt: Date

For this collection we create a special index { key: 1 }, { unique: true }, so mongo will throw an error if we try to create a new record with an existing key.

To create a lock, we use an updateOne method with an upsert = true option:

collection.updateOne(
  {
    key,
    expireAt: { $lt: new Date() },
  },
  { $set: { key, uniqueValue, expireAt: new Date(Date.now() + ttl) } },
  { upsert: true }
);

So, let's imagine that we want to create a lock and there is a valid lock in the DB. If the lock is valid, it won't pass expireAt: { $lt: new Date() } check, because its expireAt will be later than a current date. In this case updateOne will try to create a new record in the collection, because of { upsert: true } option. But it will throw an error because we have a unique index. So this operation can only be successful when there is no valid lock in the DB. If there is an invalid lock in the DB, it will be replaced by a new one.

Release and extend relies on the same logic, but we also compare with a key unique string.

You might also like...

5-BackControlFinanzas - Node & MongoDB

API para APP 'Control Finanzas' Pequeña API/Backend creada para la aplicacion personal de Control Finanzas. Enlace de github a la aplicacion que requi

Jan 3, 2022

📇 Generates and parses MongoDB BSON UUIDs

📇 Generates and parses MongoDB BSON UUIDs

uuid-mongodb Generates and parses BSON UUIDs for use with MongoDB. BSON UUIDs provide better performance than their string counterparts. Inspired by @

Nov 21, 2022

E-Commerce Application developed with nodejs and stored to mongodb.

E-Commerce Application This Application has been developed with nodejs and mongodb. Environment Variables Create a file named config.env in config dir

Dec 23, 2021

Database manager for MySQL, PostgreSQL, SQL Server, MongoDB, SQLite and others. Runs under Windows, Linux, Mac or as web application

Database manager for MySQL, PostgreSQL, SQL Server, MongoDB, SQLite and others. Runs under Windows, Linux, Mac or as web application

Database manager for MySQL, PostgreSQL, SQL Server, MongoDB, SQLite and others. Runs under Windows, Linux, Mac or as web application

Dec 30, 2022

A starter template for Nest.js with MongoDB, GraphQL, JWT Auth, and more!

A progressive Node.js framework for building efficient and scalable server-side applications. Description Nest framework TypeScript starter repository

Dec 25, 2022

An adapter-based ORM for Node.js with support for mysql, mongo, postgres, mssql (SQL Server), and more

An adapter-based ORM for Node.js  with support for mysql, mongo, postgres, mssql (SQL Server), and more

Waterline is a next-generation storage and retrieval engine, and the default ORM used in the Sails framework. It provides a uniform API for accessing

Jan 4, 2023

MongoDB object modeling designed to work in an asynchronous environment.

MongoDB object modeling designed to work in an asynchronous environment.

Mongoose Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. Mongoose supports both promises and callbacks. Do

Dec 30, 2022

The ultimate solution for populating your MongoDB database.

The ultimate solution for populating your MongoDB database.

Mongo Seeding The ultimate solution for populating your MongoDB database 🚀 Define MongoDB documents in JSON, JavaScript or even TypeScript files. Use

Dec 29, 2022

The Wholesome App. A project that allows you to upload images directly to MongoDB Atlas into your collection, a faster cloud database.

The Wholesome App. A project that allows you to upload images directly to MongoDB Atlas into your collection, a faster cloud database.

The Wholesome App. A project that allows you to upload images directly to MongoDB Atlas into your collection, a faster cloud database. To upload your cute and wholesome images.

Jul 17, 2022
Owner
Bohdan
Bohdan
🚀 A robust, performance-focused and full-featured Redis client for Node.js.

A robust, performance-focused and full-featured Redis client for Node.js. Supports Redis >= 2.6.12 and (Node.js >= 6). Completely compatible with Redi

Zihua Li 11.6k Jan 8, 2023
Modular Redis connection and PUBSUB subscription manager for node. Easily extendable. Built for performance, powered by ioredis.

RediBox Redis connection and PUBSUB subscription manager for node. Built for performance, powered by ioredis (for now). Maintained by TeamFA. What is

RediBox 83 Dec 15, 2022
A MongoDB-like database built on top of Hyperbee with support for indexing

hyperbeedeebee A MongoDB-like database built on top of Hyperbee with support for indexing WIP: There may be breaking changes in the indexing before th

null 35 Dec 12, 2022
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL and SQLite databases.

TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL and SQLite datab

MikroORM 5.4k Dec 31, 2022
A back-end server aplication created using node.js, express and mongodb.

Course Material and FAQ for my Complete Node.js, Express and MongoDB Bootcamp This repo contains starter files and the finished project files for all

Pablo César Jiménez villeda 1 Jan 4, 2022
A Node.js ORM for MySQL, SQLite, PostgreSQL, MongoDB, GitHub and serverless service like Deta, InspireCloud, CloudBase, LeanCloud.

Dittorm A Node.js ORM for MySQL, SQLite, PostgreSQL, MongoDB, GitHub and serverless service like Deta, InspireCloud, CloudBase, LeanCloud. Installatio

Waline 21 Dec 25, 2022
The Official MongoDB Node.js Driver

MongoDB NodeJS Driver The official MongoDB driver for Node.js. NOTE: v3.x released with breaking API changes. You can find a list of changes here. Ver

mongodb 9.6k Dec 28, 2022
🍹 MongoDB ODM for Node.js apps based on Redux

Lightweight and flexible MongoDB ODM for Node.js apps based on Redux. Features Flexible Mongorito is based on Redux, which opens the doors for customi

Vadim Demedes 1.4k Nov 30, 2022
A high performance MongoDB ORM for Node.js

Iridium A High Performance, IDE Friendly ODM for MongoDB Iridium is designed to offer a high performance, easy to use and above all, editor friendly O

Sierra Softworks 570 Dec 14, 2022