New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
upsert across HTTP requests has a race condition #3242
Comments
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Hi I tested it with 2.4.1 The error still occurs on my machine.
|
@henrikuper Can you please try again with |
For what it's worth, I ran in to this error when my sequence primary key id's were out of sync with the data in the table. I ran the following to see if each primary key id was correct, if not, I updated it...not sure if this'll fix your problem but here's what I did:
|
Hmm, that is quite interesting @uncvrd Do you have any idea on what caused this for your case in the first place? |
@pantharshit00 hey - so if I remember correctly, I deleted all items in my database (all the tables but didn't delete the database itself) and ran a migration to add all the same tables back, so I guess when running an upsert, the sequence ids from the old table did not reset. Hope that helps! |
Hi there! I also hit this issue. I can't create a model instance with This reproduction uses the latest version of prisma ( Hope that helps! |
I checked your reproduction and the output is expected. Since |
I'm having a similar issue, both with Here's an example schema, code and logs (table and field names have been redacted). Even though this is model Item {
id Int @id @default(autoincrement())
uniqueIndexField1 String @map("unique_index_field_1")
uniqueIndexField2 String @map("unique_index_field_2")
@@unique([uniqueIndexField1, uniqueIndexField2], name: "unique_index_field_1_unique_index_field_2_unique")
@@map("items")
} async function addItem(field1, field2) {
await prisma.item.upsert({
where: {
unique_index_field_1_unique_index_field_2_unique: {
uniqueIndexField1: field1,
uniqueIndexField2: field2
}
},
create: {
uniqueIndexField1: field1,
uniqueIndexField2: field2
},
update: {}
});
}
addItem('foo', 'bar');
addItem('foo', 'bar');
If you look at the logs, it becomes obvious what's happening. Two transactions start nearly simultaneously, both query Interestingly enough, we had similar code before with Knex, using hand-written transactions, and this rare-to-reproduce bug was present there as well. |
@Dremora I can reproduce the above issue. We should fix that. |
I'm not an expert in SQL, but below are potential solutions. An example from https://www.postgresqltutorial.com/postgresql-upsert/: INSERT INTO customers (name, email)
VALUES('Microsoft','hotline@microsoft.com')
ON CONFLICT (name)
DO
UPDATE SET email = EXCLUDED.email || ';' || customers.email; Any unique index key can be used in the Alternatively, database table would have to be locked using |
Hey folks, this is expected behavior. If you issue two upserts concurrently in the Prisma Client, under the hood we do a read, then a write. This is to support data sources that don't have native upsert support. Unfortunately, this means that your concurrent queries have a race condition and may look like this: read-read-create-create, and in this case, the second create fails the constraint. A couple workarounds:
await addItem('foo', 'bar');
await addItem('foo', 'bar');
|
Thanks @matthewmueller for the three ways workarounds, which sound very good if you have one of the three cases. Actually I have a 4. alternative which causes this error, which can not be solved with one of these three solutions. -> api endpoint is called twice at the exact same time with the same unique key. This happens nearly never in reality but theoretically it can happen. Both calls trigger the upsert function with the same unique key. Now the race condition read read create create happens and an error is thrown. I solved the issue with a dirty hack by
This doesn't completely solve the issue, but reduces the probability of occurrence enough that there are no problems in the real application. |
Thanks @henrikuper, that's a great point about upsert across requests being racy. Highlighting @Dremora's SQL to show that if two requests come in at the same time trying to update the same keys, you can end up with a unique constraint violation. prisma:query BEGIN
prisma:query BEGIN
prisma:query SELECT "public"."items"."id" FROM "public"."items" WHERE ("public"."items"."unique_index_field_1" = $1 AND "public"."items"."unique_index_field_2" = $2) OFFSET $3
prisma:query SELECT "public"."items"."id" FROM "public"."items" WHERE ("public"."items"."unique_index_field_1" = $1 AND "public"."items"."unique_index_field_2" = $2) OFFSET $3
prisma:query INSERT INTO "public"."items" ("unique_index_field_1","unique_index_field_2") VALUES ($1,$25) RETURNING "public"."items"."id"
prisma:query SELECT "public"."items"."id","public"."items"."unique_index_field_1", "public"."items"."unique_index_field_2", FROM "public"."items" WHERE "public"."items"."id" = $1 LIMIT $2 OFFSET $3
prisma:query COMMIT
prisma:query INSERT INTO "public"."items" ("unique_index_field_1","unique_index_field_2") VALUES ($1,$2) RETURNING "public"."items"."id"
prisma:query ROLLBACK This happens in two transactions (T1 & T2) running at the same time.
The solution is to use the upsert capabilities of the database itself, which turn these separate operations into one operation:
You may wonder why the second example finds Alice this time. I believe this is because operations that can write to the table need to be serialized. Since we know that this operation is an upsert, T2 will wait until T1 releases the lock on the table. |
We're running into this as well, with more or less exactly the case described here, where a user causes two nearly simultaneous http requests, which both attempt to "upsert" a user. What's especially bugging me is that we already have a retry middleware in place, but that simply keeps retrying the underlying "create", hence always failing. Our current workaround is to |
I got bitten by this too. I submitted a documentation change to add the following remark to
PR: |
🙌 |
Prisma Upserts are very powerful and support very nested upserts. This can lead to a Unique constraint error being thrown. We have updated our docs with why this happens and what to do in that situation. |
@garrensmith: Great that it's documented better. We have the case(which I see the example also have), that we have unique constraint with the combination of two fields. prisma.User.upsert({
where: {
userName_profileViews: {
userName: 'Alice',
profileViews: 1,
}
},
create: {
...
},
update: {
...
},
}) |
@t0ggah yes if your create has the two fields defined defined in the where clause and they are the same prisma.User.upsert({
where: {
userName_profileViews: {
userName: 'Alice',
profileViews: 1,
}
},
create: {
userName: 'Alice',
profileViews: 1,
},
update: {
...
},
}) |
@garrensmith thanks for adding real upserts -- super helpful. Any reason you're not supporting MySQL |
@janpio are database upserts for MySQL on the product roadmap? Referring to those using |
I fear that dropped between the other work. Can you open a feature request for this? Generally I see no reason why we should not also do similar changes/optimizations (if I am allowed to call them that) for MySQL 👍 |
@garrensmith -- this does not seem to be the case when using a multicolumn unique index. I have the same fields in both const post = await prisma.post.upsert({
create: {
name: 'Alice',
amount: 100
},
update: {},
where: {
name_amount: {
name: 'Alice',
amount: 100
}
}
}); Outputting the logs to console, it's doing a prisma client read, then insert rather than using |
Hi @garrensmith, I believe the following query satisfies the requirements to use a native upsert, but it's not. const schema = await db.destinationSchema.upsert({
where: {
destinationId: dest.id,
},
create: {
destinationId: dest.id,
envId: dest.envId,
engine: dest.engine,
projectId: dest.projectId,
},
update: {},
}) Logs show a multi-stage upsert taking place:
Am I missing something?
|
Can you please open a new issue @felipap? We'll be happy to reproduce and look into that. Thanks! |
@felipap I discovered when the |
Was this issue closed because it was fixed, if so which version? As @rothfels mentioned, in case the |
Then please open a new issue and describe the full situation. Thanks. |
Bug description
Situation: I have a model with a unique string.
At the beginning I have an empty database. I want to create or update an object of the model with upsert and check if the object exists by searching for the unique string.
If I call several upserts with the same unique string at the same time, I expect it to be created once and then updated if necessary. But it is created once and then the error occurs: Unique constraint failed on the fields.
How to reproduce
Expected behavior
I expect it to be created once and then updated the just created object, instead of crashing.
Prisma information
Environment & setup
The text was updated successfully, but these errors were encountered: