question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Concurrent updates that don't interfere with each other

See original GitHub issue

Problem

I need to get the first entry of a list of oneTimeKeys for a specific user and update it to be connected to the user that “claims” it.

What I did until now:

const oneTimeKeyResult = await prisma.oneTimeKey.findMany({ // could be findFirst
  where: {
    user: { id: userId },
    claimedByUserId: { equals: null },
  },
  take: 1,
});
const oneTimeKey = await prisma.oneTimeKey.update({
  where: { id: oneTimeKeyResult[0].id },
  data: {
    claimedByUser: { connect: { id: currentUser.id } },
  },
});

The problem here is that if two or more users try to claim the same oneTimeKey at the same time they can can and it actually happens sometimes in production.

Suggested solution

Running the find and update in a transaction would be nice, but this is not supported currently.

Alternatives

Since I’m running on Postgres (if I understand correctly) I could do a executeRaw with an update using FOR UPDATE SKIP LOCKED

https://stackoverflow.com/a/56441907/837709 https://dba.stackexchange.com/questions/69471/postgres-update-limit-1

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

3reactions
nikolasburkcommented, Nov 12, 2020

Then I though updateMany would help, but it doesn’t allow to update the relationship:

const oneTimeKey = await prisma.oneTimeKey.updateMany({
  where: {
    id: oneTimeKeyResult[0].id,
    claimedByUserId: { equals: null },
  },
  data: {
    claimedByUser: { connect: { id: currentUser.id } }, // THIS IS NOT ALLOWED
  },
});

Ah sorry, this is indeed not possible. However, with the new preview feature uncheckedScalarInput which allows to set foreign keys directly this would work:

await prisma.post.updateMany({
  where: {
    id: oneTimeKeyResult[0].id,
    claimedByUserId: { equals: null }, 
  },
  data: {
    claimedByUserId: currentUser.id // this is possible since v2.11.0
  }
})

To use this feature, you need to set the uncheckedScalarInput on the Prisma Client generator:

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["uncheckedScalarInputs"]
}

Don’t forget to run npx prisma generate afterwards (sometimes I also need to rm -rf node_modules for the latest version to show up).

2reactions
nikgrafcommented, Nov 12, 2020

@nikolasburk ahh nice, makes a lot of sense.

Just for the record in the end I went for a raw query since the queries above would have required me to write my own retry logic. If I understand correctly using FOR UPDATE SKIP LOCKED for Postgres it’s not necessary.

This post explains how queues with Postgres are possible using FOR UPDATE SKIP LOCKED https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/

Read more comments on GitHub >

github_iconTop Results From Across the Web

Concurrent Updates - USENIX
The simplest case is that of updates that don't affect the same data or meta-data. For example, two participants may have created new...
Read more >
Concurrency problems in DBMS Transactions - GeeksforGeeks
The five concurrency problems that can occur in the database are: Temporary Update Problem; Incorrect Summary Problem; Lost Update Problem ...
Read more >
Can 2 tables lock each other if concurrent read/update do not ...
Usually deadlock happens when: Query 1 tries to do an update - but before updating it needs to do a select. So first...
Read more >
Handling Concurrency Conflicts - EF Core - Microsoft Learn
Managing conflicts when the same data is updated concurrently with Entity Framework Core.
Read more >
On Concurrency Control in Databases - 7 min read - Gojek
A guide to how concurrency works in databases, and why it is important. ... Transaction 1 starts and updates all stocks_count to 10....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found