1.0.5 • Published 1 year ago

distributed-locks v1.0.5

Weekly downloads
-
License
ISC
Repository
github
Last release
1 year ago

Distributed Locks

A package for managing locks for critical section in a micro-service/horizontal deployment

Motivation

Most packages for managing access to critical sections assume a single machine deployment scenario (locks are local to the process running not across multiple process/machines), given todays standards that's very unlikely, for availability we might have replicas of the same app up and running but we still need a centralized way of managing lock across those replicas

Available Storage Layers

StorageLink
Redisdistributed-locks-redis
Postgresqldistributed-locks-postgresql
Mongodbdistributed-locks-mongodb

Usage

import LocksFactory, {FailedToObtainKey} from 'distributed-locks'
import {RedisStorage} from 'distributed-locks-redis'

const storage = new RedisStorage({ keyPrefix:'my-locks', url: redisUrl });
const locksFactory = new LockFactory(storage);

// Value of the key should represent the critical section
const lock = locksFactory.createLock('my-key');

try {
    await lock.lock({ttl: 10, interval: 5, retires: 3});
    // execute some code
    // ...
    // ...
}
catch(err){
    if(err instanceOF FailedToObtainKey) {
        // Couldn't get lock
    }
}
finally {
    await lock.unlock()
}

Storage

The different types of storage supported for maintaining locks states

Redis storage

const storage = new RedisStorage({ keyPrefix:'my-locks', url: redisUrl });

Constructor

argumentstyperequireddefaultdescription
arg0.keyPrefixstringfalse'distributed-locks'Prefix for all the keys used to make locks in redis, used to prevent conflicts with other keys
arg0.urlstringtrue-Redis connection url, should start with redis://

Postgresql storage

  const storage = new PostgresqlStorage({
      keyPrefix,
      tableName: 'locks',
      database: 'postgres',
      host: container.getHost(),
      port: container.getMappedPort(5432),
      username: 'user',
      password: 'password',
    });

Constructor

argumentstyperequireddefaultdescription
arg0.keyPrefixstringfalse'distributed-locks'Prefix for all the keys used to make locks in redis, used to prevent conflicts with other keys
arg0.portnumbertrue-Port of the Postgresql server
arg0.hoststringtrue-Host of the Postgresql server
arg0.databasestringtrue-Name of the database
arg0.usernamestringtrue-Username of the Postgresql server
arg0.passwordstringtrue-Password of the username provided
arg0.tableNamestringfalselocksThe table name that will be used to store locks states

MongoDB storage

const storage = new MongoStorage({ url: 'mongodb://.....', database: 'db', collectionName: 'locks'});

Constructor

argumentstyperequireddefaultdescription
arg0.urlstringtrue-Redis connection url, should start with redis://
arg0.databasestringtrue-Name of the database
arg0.collectionNamestringfalselocksThe collection name that will be used to store locks states

Locks Factory

Constructor

argumentstyperequireddefaultdescription
storageIStoragetrue-The storage instance that will be used by locks to try and lock critical section

Create lock

const lock = locksFactory.createLock('my-key');
argumentstyperequireddefaultdescription
keystringtrue-The key that will be used to lock a certain critical section

Lock

Lock

await lock.lock({ttl: 10, interval: 5, retires: 3});
argumentstyperequireddefaultdescription
arg0.ttlnumberfalse10The time to live for locking the critical section to prevent starvation, should be more than the expected time of work that needs to be done while obtaining a lock
arg0.retriesnumberfalse3The number of times to try to obtain the lock, if the last try fails the function will throw an error
arg0.intervalnumberfalse1The interval of time waiting in seconds between each retry to obtain the lock

unlock

await lock.unlock();
argumentstyperequireddefaultdescription

Tests

To run tests locally you need a running docker host, as some tests spawn up docker containers

Guarantees

  • At any given time, for the same value for the key, only one lock can be locked
  • Locking doesn't cause starvation as there is a ttl on each lock obtained
  • If the locks ttl expires and unlock was called on the lock, it won't unlock that section if it was already obtained by another lock

Redis

Guarantees are done by applying the red-lock algorithm and lua scripts to provide atomicity of any operation

Postgres

Guarantees are done by using REPEATABLE READ ISOLATION level for transactions to set/delete keys, the statement is a single insert statement with ON CONFLICT with the key value condition, updates the current row only if it already expired (checkout the postgres storage file).

MongoDB

Using mongo default atomicity guarantees on single document, and unique index on the _id field

0.2.0

1 year ago

1.0.5

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

0.1.2

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago

0.0.5

1 year ago

0.0.4

1 year ago

0.0.1

1 year ago