Preventing concurrent updates to ensure every thread gets a unique ID value

Hello

I have a single value which gets constantly updated. I use findOneAndUptaed since I want to get the field “value” back in my response.
The questions are;

  • Will this be a save approach when I have concurrent threats trying to update the SystemID?
  • What if, at the very same nano second, two thread try to update?
  • How are these updates in the copy on write process handled?
  • Will both threads read the same value and update in parallel two versions of the document?
  • What would be a result if the above is true?

Is there a simple way to prevent the concurrent updates? Maybe even to the lack of performance e.g. exclusive locking the document?

The final goal is that every thread gets a unique SystemID back.

// cleanup
db.incTest.drop();

// insert testdata
db.incTest.insert([
  { '_id' : 1, 'key' : 'lastGivenSystemID', 'value' : 1234567890123456 }
]);

// update SystemID by +1
db.incTest.findOneAndUpdate(
   { key: "lastGivenSystemID" },
   { $inc: { value: 1 } },
   { returnNewDocument: true }
)
1 Like

The Atomicity and Transactions documentation says:

In MongoDB, a write operation is atomic on the level of a single document, even if the operation modifies multiple embedded documents within a single document.

In some scenarios you can use Multi-Document Transactions. This requires MongoDB deployments with versions 4.0 (for replica-sets) and 4.2 (for sharded clusters). Note that this feature is not available on standalone deployments.

Hi @Prasad_Saya

thanks for the update, since the threads are complete independent Mulit-Doc Updates will not help. The use case is that single request hammer on this field, the final goal is that every thread gets a unique SystemID back.

Concerning Atomicity: sure a write is atomic but what about the query part of the update ? What happens when two request want to update and “find” the same document. The does the copy on write process allow for two versions of document? If so I think we have optimistic locking her, so the last one wins? But would be return values one would be an update SystemID, the other?? The same SystemID? Or will it fail?

If 1000 clients (each client as a thread) does this update operation and returns the updated value, will the value be unique each time? Yes, I think it will.

I tried the update concurrently on the same operation from a Java client, and after each update I get a new (and unique value) returned by the update.



The Java code I used:
private void go() throws InterruptedException {
	// Create an instance of MongoClient with default values - 
	// with a connection pool of 100 (default for Java driver).
	try(MongoClient mongoClient = MongoClients.create()) {
		MongoCollection<Document> coll = mongoClient.getDatabase("test").getCollection("th1");
		Bson update = inc("value", new Integer(1));
		FindOneAndUpdateOptions updateOptions = new FindOneAndUpdateOptions()
													.returnDocument(ReturnDocument.AFTER);
		Bson queryFilter = eq("key","lastGivenSystemID");
		testWithConcurrency(coll, queryFilter, update, updateOptions);
	}
}
	
private void testWithConcurrency(MongoCollection<Document> coll, 
									Bson queryFilter, 
									Bson update,
									FindOneAndUpdateOptions updateOptions)
		throws InterruptedException {

	int numberOfThreads = 5000;
	ExecutorService service = Executors.newFixedThreadPool(20);
	CountDownLatch latch = new CountDownLatch(numberOfThreads);

	for (int i = 0; i < numberOfThreads; i++) {
		service.submit(() -> {
			update(coll, queryFilter, update, updateOptions);
			latch.countDown();
		});
	}
	latch.await();
	service.shutdown();

	// This verified on different thread pool sizes and number of created threads for update.
	System.out.println(">>> Verify numberOfThreads, returnedValues: " + numberOfThreads + ", " + set.size());
}

// NOTE: The results were the same without the synchronization on this method.
private synchronized void update(MongoCollection<Document> coll, 
									Bson queryFilter, 
									Bson update,
									FindOneAndUpdateOptions updateOptions) {
	Document result = coll.findOneAndUpdate(queryFilter, update, updateOptions);
	Integer i = ((Double) result.get("value")).intValue();
	set.add(i); // set is an instance of CopyOnWriteArraySet
	//System.out.println(">>> " + Thread.currentThread() + ": " + i);
}
1 Like

Yes, the query and write part of the update are guaranteed to be atomic, so you should not be using any additional locking on the application side, the correct thing will happen server-side.

4 Likes