simo
simo

Reputation: 24560

Handling concurrent tasks

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

Answers (2)

vfsoraki
vfsoraki

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

Dogbert
Dogbert

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:

  1. Add a unique_index to the table using a migration.

  2. Add a call to unique_constraint in your changeset function.

  3. 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

Related Questions