Deno's first lightweight, secure distributed lock manager utilizing the Redlock algorithm

Overview

Deno-Redlock

Description

This is an implementation of the Redlock algorithm in Deno. It is a secure, lightweight solution to control resource access in distributed systems architecture.

Distributed locks are a very useful primitive in many environments where different processes require to operate with shared resources in a mutually exclusive way.

There are a number of libraries and blog posts describing how to implement a DLM (Distributed Lock Manager) with Redis, but every library uses a different approach, and many use a simple approach with lower guarantees compared to what can be achieved with slightly more complex designs.

https://redis.io/docs/reference/patterns/distributed-locks/

Installation

import Redlock from "https://deno.land/x/redlock/mod.ts"

Documentation

Deno DLM

Configuration

Instantiate a Redlock object by passing an array of at least one Redis client (for storing lock data) and an optional options object. Do NOT change properties of the Redlock object after instantiation. Doing so could have unintended consequences on live locks.

import { connect } from "https://deno.land/x/redis/mod.ts"
import Redlock from "https://deno.land/x/redlock/mod.ts"

const redisA = await connect({hostname: "HostIpAddress", port: portNumber})
const redisB = await connect({hostname: "HostIpAddress", port: portNumber})
const redisC = await connect({hostname: "HostIpAddress", port: portNumber})

const redlock = new Redlock(
  // One client per each independent Redis node/cluster
  [redisA, redisB, redisC],
  {
    // The expected clock drift; for more details see:
    // http://redis.io/topics/distlock
    driftFactor: 0.01, // multiplied by lock time to live to determine drift time

    // The max number of times Redlock will attempt to lock a resource before erroring
    // setting retryCount: -1 allows for unlimited retries until the lock is acquired
    retryCount: 10,

    // the time in ms between attempts
    retryDelay: 200, // time in ms

    // the max time in ms randomly added to retries
    // to improve performance under high contention
    // see https://www.awsarchitectureblog.com/2015/03/backoff.html
    retryJitter: 200, // time in ms

    // The minimum remaining time on a lock before an extension is automatically
    // attempted with the `using` API.
    automaticExtensionThreshold: 500, // time in ms
  }
);

Additionally, the Lua scripts used to acquire, extend, and release the locks may be customized on Redlock instantiation. Please view the source code if required.

Lock Usage

The using method allows a routine to be executed within the context of an auto-extending lock. This method returns a promise that resolves to the routine's value. If the auto-extension fails, then the routine is aborted through the use of an AbortSignal.

The first parameter represents an array of resources that one wishes to lock. The second parameter is the desired lock duration in milliseconds (given as an integer).

await redlock.using(["{redlock}resourceId", "{redlock}resourceId2"], 10000, async (signal) => {
  // perform some action using the locked resources...
  const resource = await getResource(resourceId);
  const resource2 = await getResource(resourceId2);

  // The abort signal will be true if:
  // 1. the above took long enough that the lock needed to be extended
  // 2. redlock was unable to extend the lock
  //
  // In such a case, exclusivity can no longer be guaranteed for further operations
  // and should be handled as an exceptional case.
  if (signal.aborted) {
    throw signal.error;
  }

  // perform other actions with the resources...
  await updateResources([
    {id: resourceId, resource: updatedResource},
    {id: resourceId2, resource: updatedResource2},
  ]);
});

Locks can also be acquired, extended, and released manually

// acquisition
let lock = await redlock.acquire(["exampleResourceId"], 10000);
try {
  // perform some action with locked resource...
  await action();

  // extension, which instantiates a new Lock
  lock = await lock.extend(10000);

  // perform another action...
  await anotherAction();
} finally {
  // release
  await lock.release();
}

Note: commands are executed in a single call to Redis. Redis clusters require that all keys in a command must hash to the same node. When acquiring a lock on multiple resources while using Redis clusters, redis hash tags must be used to ensure that all keys are allocated in the same hash slot. The most straightforward strategy is to use a single generic prefix inside hash tags before listing the resource, such as {redlock}resourceId. This ensures that all lock keys resolve to the same node and may be appropriate when the cluster storing the lock data has additional purposes. When all resources share a common attribute (such as organizationId), this attribute can be used inside the hash tags for better key distribution and cluster utilization. If you do not need to lock multiple resources at the same time or are not using clusters, omit the hash tags to achieve ideal utilization.

Error Handling

Redlock is designed for high availability and doesn't care if a minority of Redis instances/clusters fail at an operation. However, it may be helpful to monitor or log normal usage errors. A per-attempt and per-client stats object (including errors) is made available on the attempts property of both the Lock and ExecutionError classes. Additionally, Redlock emits an "error" event whenever an error is encountered, even if the error is ignored and normal operation continues.

redlock.on("error", (error) => {
  // Ignore cases where a resource is explicitly marked as locked on a client
  if (error instanceof ResourceLockedError) {
    return;
  }
  
  // Log all other errors
  console.error(error);
});

Disclaimer

This code implements an algorithm which is currently a proposal and was not formally analyzed. Make sure to understand how it works before using it in your production environments.

See Martin Kleppmann's analysis and Salvatore Sanfilippo's counterpoint to this analysis.

A note about time:

Redis and Deno-Redlock utilize a monotonic time API to prevent errors due to random time jumps that are possible with a poorly maintained GPS time API.

The Development Team

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b your-new-feature)
  3. Commit your changes (git commit -am 'feature-added')
  4. Push to the branch (git push origin your-new-feature)
  5. Create a new Pull Request

Credit

Big thanks to Mike Marcacci for the Node.js implementation of the Redlock algorithm.

You might also like...

Provides Lock and RwLock synchronization primitives.

Lock Provides Lock and RWLock (read write lock) synchronization primitives for protecting in-memory state across multiple tasks and/or microtasks. Ins

Apr 27, 2022

Deduplication tool for pnpm-lock.yaml files

pnpm-deduplicate Remove duplicate dependencies from pnpm-lock.yaml. This project is simple and not have many features. I see it as a temporary solutio

Jan 3, 2023

A simple lock-and-release eventually-consistent DB to be consumed by multi-threaded applications in node.

worsen - a persistence store for quick prototyping A simple lock-and-release eventually-consistent DB to be consumed by multi-threaded applications in

Oct 1, 2022

A devtool improve your pakage manager use experience no more care about what package manager is this repo use; one line, try all.

pi A devtool improve your pakage manager use experience no more care about what package manager is this repo use; one line, try all. Stargazers over t

Nov 1, 2022

Meogic-tab-manager is an extensible, headless JavaScript tab manager framework.

Meogic-tab-manager is an extensible, headless JavaScript tab manager framework.

MeogicTabManager English document MeogicTabManager是一个有可拓展性的、headless的JavaScript标签页管理框架。 MeogicTabManager旨在提供可自由组装页面框架、自定义页面组件、甚至覆盖框架自带事件响应的开发体验。 Meogi

Oct 8, 2022

⚡ the first open-source redis client made with care and acessibility-first 🚀

⚡ Redis UI The first open-source project to create an awesome and accessible UI for Redis as a native desktop application. ✨ 🦄 🚀 How to develop loca

Dec 5, 2022

distributed-nginx nginx k8s docker micro front-end

distributed-nginx (分布式 nginx) 🍙 适用于微前端的去中心化分布式部署 nginx 服务器. 特性 支持 前端服务上线下线 自动更新微前端模块配置 完全实现了分布式去中心化 支持【微前端组】 支持 redis 协议和 multicast-dns 协议 支持 命名空间 Ge

Feb 25, 2022

"Lerna & Distributed Task Execution" Example

Lerna Distributed Task Execution (DTE) Example/Benchmark On how to make your CI 23 times faster with a small config change New versions of Lerna can u

Nov 27, 2022

The Gitcoin Passport SDK is comprised of a set of libraries distributed on npm to help developers interact with Passport data living on Ceramic.

The Gitcoin Passport SDK is comprised of a set of libraries distributed on npm to help developers interact with Passport data living on [Ceramic]

Dec 6, 2022
Comments
  • suggestion: testing instructions

    suggestion: testing instructions

    Hi team, I'd like to contribute to this great module. However, there aren't any testing instructions. Is it possible to have this added so contributors can do so?

    opened by iuioiua 1
Releases(v1.0)
Owner
OSLabs Beta
OSLabs Beta
The first ever MC:BE ForceOP Exploit utilizing a user impersonation exploit within Bedrock Dedicated Server

EliteElixir The first ever MC:BE ForceOP Exploit utilizing a user impersonation exploit within Bedrock Dedicated Server This tool uses the sub_client_

null 28 Jul 27, 2023
Non-interactive publicly verifiable distributed key generation and resharing algorithm over BLS12-381

NPVDKG-RS This repository contains a mathematical presentation and some code to demonstrate our developed non-interactive publicly verifiable distribu

NATRIX Official 8 May 19, 2022
A pointer lock movement manager for customizing your own creative UI.

Pointer Lock Movement A pointer lock movement manager for customizing your own creative UI. Inspired by Figma's number input element: Dragging on an i

Zheeeng 8 Nov 4, 2022
Hyperdrive is a secure, real-time distributed file system

Hyperdrive Hyperdrive is a secure, real-time distributed file system Install npm install hyperdrive@next Note this is the Hyperdrive 11 preview based

Hypercore Protocol 28 Dec 28, 2022
Seamless and lightweight parallax scrolling library implemented in pure JavaScript utilizing Hardware acceleration for extra performance.

parallax-vanilla.js Seamless and lightweight parallax scrolling library implemented in pure JavaScript utilizing Hardware acceleration for extra perfo

Erik Engervall 91 Dec 16, 2022
tb-grid is a super simple and lightweight 12 column responsive grid system utilizing css grid.

tb-grid Lightweight (<1kb gzipped) 12 column grid system, built with css grid. ?? Demos & Playground Have a look at those examples: Main Demo: https:/

Taskbase 26 Dec 28, 2022
A Secure Web Proxy. Which is fast, secure, and easy to use.

Socratex A Secure Web Proxy. Which is fast, secure, and easy to use. This project is under active development. Everything may change soon. Socratex ex

Leask Wong 220 Dec 15, 2022
A community-led token scanner for Replit utilizing its own APIs.

Replit Token Scanner A community-led project that aims to scan published Repls to find secrets and invalidate them. Usage This repo contains the scann

Ray 18 Nov 6, 2022
Lightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more

Apache MXNet (incubating) for Deep Learning Apache MXNet is a deep learning framework designed for both efficiency and flexibility. It allows you to m

The Apache Software Foundation 20.2k Jan 5, 2023
Vesting contract with multiple beneficiaries, tokens, and lock schedules.

⚠️ This code has not been audited, use at your own risk Vesting Contract This contract handles the vesting of ERC20 tokens for multiple beneficiaries.

DeFi Wonderland 5 Mar 14, 2022