c4710n

c4710n

Is `Repo.insert_or_update` safe for race condition?

I have already used upsert feature via Repo.insert/2 for a long time.

Today, when I read other’s code, I found something like this:

case MyRepo.get(Subscription, id) do
  nil  -> %Subscription{id: id}
  subscription -> subscription
end
|> Subscription.changeset(changes)
|> MyRepo.insert_or_update()

I’m wondering:

  1. is this usage safe for race condition?
  2. Should I use Ecto.Mulit.insert_or_update instead?
  3. If it’s not safe, why MyRepo.insert_or_update exists?

An example for race condition:

  1. A try to get subscription with id reminder, get nil, then create a new subscription struct.
  2. B try to get subscription with id reminder , and get nil, then create a new subscription struct.
  3. A try to insert, done.
  4. B try to insert, rejected.

Marked As Solved

trisolaran

trisolaran

  1. No, this is a classic example of a race condition, which you identified in your example (A and B trying to insert a record with the same id)
  2. Multi is kind of irrelevant with respect to race conditions, I assume what you’re asking is “would wrapping this code in a transaction help?”. The answer is: no, it won’t help in the general case. It would only help if the transaction isolation level of your DB is set to “serializable”, which is typically not the case for performance reasons.
  3. insert_or_update is not there to avoid race conditions, but as a convenience function that allows you to persist a changeset regardless of whether the underlying data is persisted in the DB or not (otherwise you’d have to choose insert or update)

You should check the result of MyRepo.insert_or_update(): if the result is {:error, changeset} and the changeset contains a primary key constraint violation, then you know that you hit the race condition and you can react appropriately.

Also Liked

thousandsofthem

thousandsofthem

https://hexdocs.pm/ecto/constraints-and-upserts.html#upserts

I think you need this. Single sql query, no concurrency issues

03juan

03juan

Surely B’s insert would be rejected only if there’s a uniqueness constraint on the fields being updated? Otherwise you’ll get two rows with identical data but different ids.

If there’s a constraint then you can catch the error after the insert_or_update, find the existing id, and then re-run the function, which will update.

trisolaran

trisolaran

Yes pretty much: ecto/lib/ecto/repo/schema.ex at 960725b9e0719c79d8296c4852072780e8d5cc3e · elixir-ecto/ecto · GitHub

Also check @thousandsofthem’s suggestion. That’s a good point and might make your code simpler.

Where Next?

Popular in Questions Top

vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement