Ecto.Model.OptimisticLock

Facilities for using the optimistic-locking technique.

Optimistic locking (or optimistic concurrency control) is a technique that allows concurrent edits on a single record. While pessimistic locking works by locking a resource for an entire transaction, optimistic locking only checks if the resource changed before updating it.

This is done by regularly fetching the record from the database, then checking whether another process has made changes to the record only when updating the record. This behaviour is ideal in situations where the chances of concurrent updates to the same record are low; if they’re not, pessimistic locking or other concurrency patterns may be more suited.

Usage

Optimistic locking works by keeping a “version” counter for each record; this counter gets incremented each time a modification is made to a record. Hence, in order to use optimistic locking, a column must be added to a given model’s table and a field must be added to that model’s schema.

Examples

Assuming we have a Post model (stored in the posts table), the first step is to add a version column to the posts table:

alter table(:posts) do
  add :lock_version, :integer, default: 1
end

The column name is arbitrary and doesn’t need to be :lock_version. However, it needs to be an integer.

Now a field must be added to the schema and the optimistic_lock/1 macro has to be used in order to specify which column in the schema will be used as the “version” column.

defmodule Post do
  use Ecto.Model

  schema "posts" do
    field :title, :string
    field :lock_version, :integer, default: 1
  end

  optimistic_lock :lock_version
end

Note that the optimistic_lock/1 macro is defined in this module, which is imported when Ecto.Model is used. To use the optimistic_lock/1 macro without using Ecto.Model, just use Ecto.Model.OptimisticLock but be sure to use Ecto.Model.Callbacks as well since it’s used by Ecto.Model.OptimisticLock under the hood.

When a conflict happens (a record which has been previously fetched is being updated, but that same record has been modified since it was fetched), an Ecto.StaleModelError exception is raised.

iex> post = Repo.insert!(%Post{title: "foo"})
%Post{id: 1, title: "foo", lock_version: 1}
iex> valid_change = cast(%{title: "bar"}, post, ~w(title), ~w())
iex> stale_change = cast(%{title: "baz"}, post, ~w(title), ~w())
iex> Repo.update!(valid_change)
%Post{id: 1, title: "bar", lock_version: 2}
iex> Repo.update!(stale_change)
** (Ecto.StaleModelError) attempted to update a stale model:

%Post{id: 1, title: "baz", lock_version: 1}

Optimistic locking also works with delete operations: when trying to delete a stale model, an Ecto.StaleModelError exception is raised as well.

Source

Summary

optimistic_lock(field)

Specifies a field to use with optimistic locking

Macros

optimistic_lock(field)

Specifies a field to use with optimistic locking.

This macro specifies a field that will be used to implement the optimistic-locking technique described in the docs for this module.

optimistic_lock/1 can be used multiple times per model.

Examples

defmodule Note do
  use Ecto.Model

  schema "notes" do
    add :title, :string
    add :body, :text
    add :optlock, :integer, default: 1
  end

  optimistic_lock :optlock
end
Source