Skip to content

Mongo Distributed Lock

MongoDB connector enables distributed locking on MongoDB. It was tested on MongoDB v3.6.

Mongo Client

There is no need for a special MongoClient configuration. Default settings, where all writes use master node, are sufficient. Sherlock uses no read queries and only the following modification operations: findOneAndReplace, findOneAndDelete, deleteMany.

Usage

Add dependencies to build.gradle.kts:

dependencies {
    implementation("com.coditory.sherlock:sherlock-mongo:0.4.19")
}
dependencies {
    implementation("com.coditory.sherlock:sherlock-mongo-coroutines:0.4.19")
}
dependencies {
    implementation("com.coditory.sherlock:sherlock-mongo-reactor:0.4.19")
}
dependencies {
    implementation("com.coditory.sherlock:sherlock-mongo-rxjava:0.4.19")
}

Create sherlock instance and distributed lock:

import com.coditory.sherlock.DistributedLock;
import com.coditory.sherlock.Sherlock;
import com.coditory.sherlock.mongo.MongoSherlock;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MongoSyncLockSample {
    private static final Logger logger = LoggerFactory.getLogger(MongoSyncLockSample.class);

    private static MongoCollection<Document> getCollection() {
        String database = "sherlock";
        String connectionString = "mongodb://localhost:27017/" + database;
        MongoClient mongoClient = MongoClients.create(connectionString);
        return mongoClient
            .getDatabase("sherlock")
            .getCollection("locks");
    }

    public static void main(String[] args) {
        Sherlock sherlock = MongoSherlock.create(getCollection());
        DistributedLock lock = sherlock.createLock("sample-lock");
        lock.runLocked(() -> logger.info("Lock acquired!"));
    }
}
import com.coditory.sherlock.mongo.coroutines.MongoSherlock
import com.mongodb.kotlin.client.coroutine.MongoClient
import com.mongodb.kotlin.client.coroutine.MongoCollection
import kotlinx.coroutines.runBlocking
import org.bson.Document
import org.slf4j.Logger
import org.slf4j.LoggerFactory

object MongoKtLockSample {
    private val logger: Logger = LoggerFactory.getLogger(this.javaClass)

    private fun getCollection(): MongoCollection<Document> {
        val database = "sherlock"
        val mongoClient = MongoClient.create("mongodb://localhost:27017/$database")
        return mongoClient
            .getDatabase(database)
            .getCollection<Document>("locks")
    }

    private suspend fun sample() {
        val sherlock = MongoSherlock.create(getCollection())
        val lock = sherlock.createLock("sample-lock")
        lock.runLocked { logger.info("Lock acquired!") }
    }

    @JvmStatic
    fun main(args: Array<String>) {
        runBlocking { sample() }
    }
}
import com.coditory.sherlock.mongo.reactor.MongoSherlock;
import com.coditory.sherlock.reactor.DistributedLock;
import com.coditory.sherlock.reactor.Sherlock;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import com.mongodb.reactivestreams.client.MongoCollection;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MongoReactorLockSample {
    private static final Logger logger = LoggerFactory.getLogger(MongoReactorLockSample.class);

    private static MongoCollection<Document> getCollection() {
        String database = "sherlock";
        MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017/" + database);
        return mongoClient
            .getDatabase("sherlock")
            .getCollection("locks");
    }

    public static void main(String[] args) {
        Sherlock sherlock = MongoSherlock.create(getCollection());
        DistributedLock lock = sherlock.createLock("sample-lock2");
        lock.runLocked(() -> logger.info("Lock acquired!"))
            .block();
    }
}
import com.coditory.sherlock.mongo.rxjava.MongoSherlock;
import com.coditory.sherlock.rxjava.DistributedLock;
import com.coditory.sherlock.rxjava.Sherlock;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import com.mongodb.reactivestreams.client.MongoCollection;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MongoRxLockSample {
    private static final Logger logger = LoggerFactory.getLogger(MongoRxLockSample.class);

    private static MongoCollection<Document> getCollection() {
        String database = "sherlock";
        MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017/" + database);
        return mongoClient
                .getDatabase("sherlock")
                .getCollection("locks");
    }

    public static void main(String[] args) {
        Sherlock sherlock = MongoSherlock.create(getCollection());
        DistributedLock lock = sherlock.createLock("sample-lock");
        lock.runLocked(() -> logger.info("Lock acquired!"))
                .blockingGet();
    }
}

Learn more

See full source code example on Github.

Configuration

Configuration is available via sherlock builder:

MongoSherlock.builder()
    .withClock(Clock.systemUTC())
    .withLockDuration(Duration.ofMinutes(5))
    .withUniqueOwnerId()
    .withLocksCollection(getCollection())
    .build();
MongoSherlock.builder()
    .withClock(Clock.systemUTC())
    .withLockDuration(Duration.ofMinutes(5))
    .withUniqueOwnerId()
    .withLocksCollection(getCollection())
    .build()
MongoSherlock.builder()
    .withClock(Clock.systemUTC())
    .withLockDuration(Duration.ofMinutes(5))
    .withUniqueOwnerId()
    .withLocksCollection(getCollection())
    .build();
MongoSherlock.builder()
    .withClock(Clock.systemUTC())
    .withLockDuration(Duration.ofMinutes(5))
    .withOwnerIdPolicy(OwnerIdPolicy.uniqueOwnerId())
    .withLocksCollection(getCollection())
    .build();

Parameters:

  • clock (default: Clock.systemUTC()) - used to generate acquisition and expiration timestamps.
  • lockDuration (default: Duration.ofMinutes(5)) - a default lock expiration time. If lock is not released and expiration time passes, the lock is treated as released.
  • ownerIdPolicy (default: uniqueOwnerId()) - used to generate lock owner id. It's executed once for every lock, during lock creation. There are different policies available for generating lock ownerIds.
  • locksCollection - MongoDb collection used to store the locks.

Locks collection

Sample lock document:

{
  // Lock id
  "_id": "lock-id",
  // Owner id
  "acquiredBy": "aec5229a-1728-4200-b8d1-14f54ed9ac78",
  // Lock acquisition moment
  "acquiredAt": { "$date": "2024-03-20T08:03:02.231Z" },
  // Lock expiation time.
  // Might be null for locks that do not expire
  "expiresAt": { "$date": "2024-03-20T08:08:02.231Z" }
}