`retry_on_stale/2` - retry operations encountering Ecto's stale entry errors

Elixir utility for retrying operations encountering Ecto’s stale entry errors (Ecto.StaleEntryError).

A more lightweight version of the retry library specifically designed to handle stale entry errors when using optimistic locking, requiring subsequent attempts.

import RetryOnStale, only: [retry_on_stale: 2]

def increase_wallet_balance(%Wallet{} = wallet, amount) do
  retry_on_stale(
    fn attempt ->
      # refetch the latest wallet data for subsequent attempts
      wallet = if attempt == 1, do: wallet, else: Repo.get!(Wallet, wallet.id)

      # this function could raise a StaleEntryError
      do_increase_wallet_balance(wallet, amount)
    end,
    max_attempts: 5, delay_ms: 100
  )
end

defp do_increase_wallet_balance(%Wallet{} = wallet, amount) do
  new_balance = Decimal.add(wallet.balance_amount, amount)

  wallet
  |> Ecto.Changeset.change(balance_amount: new_balance)
  |> Ecto.Changeset.optimistic_lock(:lock_version)
  |> Repo.update!()
end

GitHub repo:

1 Like

You probably know this, but for others reading, you can also let the DB take care of the increment/decrement:

  def increase_wallet_balance(wallet_id, by_amount) do
    from(w in Wallet,
      update: [inc: [balance_amount: ^by_amount]],
      where: w.id == ^wallet_id
    )
    |> Repo.update_all([])
  end

  def decrease_wallet_balance(wallet_id, by_amount) do
    increase_wallet_balance(wallet_id, -by_amount)
  end
2 Likes