Cache.Counter (elixir_cache v0.4.5)

View Source

Atomic integer counter adapter backed by Erlang's :counters module.

Counter values are stored in a lock-free :counters array. The array reference is stored in :persistent_term so all processes can access it without a process round-trip.

The slot index for a key is determined as follows:

  • Integer key — used directly as the 0-based slot index (key + 1 internally, since :counters is 1-based). Callers control exactly which slot is accessed with no hashing involved.
  • Atom / string key — index computed deterministically via :erlang.phash2(key, size) + 1, eliminating any key-to-index bookkeeping and the race conditions that come with it.

Behaviour

  • put/4 accepts only 1 or -1 as values, acting as increment or decrement. Any other value returns an error.
  • get/2 accepts only a non-negative integer key and returns the current integer value for that slot. Returns 0 if the slot has never been incremented.
  • delete/2 zeroes the counter slot for the given key. Because multiple keys may hash to the same slot (especially with a small initial_size), deleting one key resets the slot shared by all keys that collide with it.
  • increment/1,2 and decrement/1,2 are injected into consumer modules via use.

Hash collisions

With a small initial_size, distinct keys may map to the same counter slot. Operations on colliding keys are summed in that shared slot. Increase initial_size to reduce collision probability.

Options

  • :initial_size (pos_integer/0) - Number of counter slots to pre-allocate The default value is 1.

  • :write_concurrency (boolean/0) - Enable concurrent writes to different counter slots. When false, all writes serialize through a single process. The default value is false.

Example

defmodule MyApp.Cache do
  use Cache,
    adapter: Cache.Counter,
    name: :my_app_counter_cache,
    opts: [initial_size: 32]
end

MyApp.Cache.increment(:page_views)
MyApp.Cache.decrement(:active_users)
{:ok, count} = MyApp.Cache.get(:page_views)

Summary

Functions

Returns a specification to start this module under a supervisor.

Functions

child_spec(arg)

Returns a specification to start this module under a supervisor.

arg is passed as the argument to Task.start_link/1 in the :start field of the spec.

For more information, see the Supervisor module, the Supervisor.child_spec/2 function and the Supervisor.child_spec/0 type.

decrement(cache_name, key, step \\ 1)

@spec decrement(atom(), atom() | String.t() | non_neg_integer(), pos_integer()) ::
  :ok | ErrorMessage.t()

increment(cache_name, key, step \\ 1)

@spec increment(atom(), atom() | String.t() | non_neg_integer(), pos_integer()) ::
  :ok | ErrorMessage.t()