Reputation: 24560
I have multiple concurrent tasks, all of them trying to check record existance, then, if not exist, will insert one.
Unfortunately, I end up with duplicate writing to the record into DB, as it seems that all of tasks decides that the record does not exist at the same time, then all of them do insertion.
The desired behavior, is that I got the insertion only once, and then, other tasks would recognize the existence of just inserted record.
Here is my attempt:
alias MyApp.Parent, as: Parent
alias MyApp.Repo, as: Repo
changeset = Parent.changeset(%Parent{}, model)
case Repo.all(from p in Parent, where: p.mobile_number == ^model.mobile_number) do
[] ->
#does not exist
case Repo.insert_or_update(changeset) do
{:ok, %MyApp.Parent{ id: parent_id }} -> parent_id
error_message -> nil
end
[parent_get_by|t] ->
#already exist
%MyApp.Parent{ id: parent_id }= parent_get_by
parent_id
end
Any help is appreciated!
Upvotes: 1
Views: 128
Reputation: 2307
The accepted answer is fine.
But when you can't enforce a unique index, you have two choices. First is using a lock, to prevent processes querying db at the same time. See elixir_locker for an example of this.
Another way is to serialize the requests. This means, you will have a process, preferably a GenServer
, that will do the SELECT+INSERT
. Then have you processes send a message to this, instead of querying db themselves. This makes the requests run after one another, and only the first request will insert, others will read. Of course, this process may become a bottleneck itself, if you have a lot of requests.
Upvotes: 0
Reputation: 222108
You should add a UNIQUE INDEX
in your database for the mobile_number
field. It will be more efficient (you'll only have to do one query to the database) and you are guaranteed that the database can never ever have a duplicate value for that field in the table.
You'll need to do three things:
Add a unique_index
to the table using a migration.
Add a call to unique_constraint
in your changeset function.
In your controller, just do Repo.insert(changeset)
. If the field is duplicated, you'll get {:error, changeset}
back with the error message in changeset.errors
.
Upvotes: 2