ConCache

Implements an ETS based key/value storage with following additional features:

Example usage:

ConCache.start_link([], name: :my_cache)
ConCache.put(:my_cache, :foo, 1)
ConCache.get(:my_cache, :foo)  # 1

The following rules apply:

See start_link/2 for more details.

Source

Summary

delete(cache_id, key)

Deletes the item from the cache

dirty_delete(cache_id, key)

Dirty equivalent of delete/2

dirty_get_or_store(cache_id, key, store_fun)

Dirty equivalent of get_or_store/3

dirty_insert_new(cache_id, key, value)

Dirty equivalent of insert_new/3

dirty_put(cache_id, key, value)

Dirty equivalent of put/3

dirty_update(cache_id, key, update_fun)

Dirty equivalent of update/3

dirty_update_existing(cache_id, key, update_fun)

Dirty equivalent of update_existing/3

ets(cache_id)

Returns the ets table managed by the cache

get(cache_id, key)

Reads the item from the cache

get_or_store(cache_id, key, store_fun)

Retrieves the item from the cache, or inserts the new item

insert_new(cache_id, key, value)

Inserts the item into the cache unless it exists

isolated(cache_id, key, timeout \\ nil, fun)

Isolated execution over arbitrary lock in the cache

put(cache_id, key, value)

Stores the item into the cache

start(options \\ [], gen_server_options \\ [])

Starts the server

start_link(options \\ [], gen_server_options \\ [])

Starts the server and creates an ETS table

touch(cache_id, key)

Manually touches the item to prolongate its expiry

try_isolated(cache_id, key, timeout \\ nil, on_success)

Similar to isolated/4 except it doesn’t wait for the lock to be available

update(cache_id, key, update_fun)

Updates the item, or stores new item if it doesn’t exist

update_existing(cache_id, key, update_fun)

Updates the item only if it exists. Otherwise works just like update/3

Types

t :: pid | atom | {:global, any} | {:via, atom, any}

key :: any

value :: any

callback_fun :: ({:update, pid, key, value} | {:delete, pid, key} -> any)

ets_option :: :named_table | :compressed | {:heir, pid} | {:write_concurrency, boolean} | {:read_concurrency, boolean} | :ordered_set | :set | {:name, atom}

options :: [ttl: non_neg_integer, acquire_lock_timeout: pos_integer, callback: callback_fun, touch_on_read: boolean, ttl_check: non_neg_integer, time_size: pos_integer, ets_options: [ets_option]]

update_fun :: (value -> {:ok, store_value} | {:error, any})

store_fun :: (() -> store_value)

Functions

delete(cache_id, key)

Specs:

  • delete(t, key) :: :ok

Deletes the item from the cache.

Source
dirty_delete(cache_id, key)

Specs:

  • dirty_delete(t, key) :: :ok

Dirty equivalent of delete/2.

Source
dirty_get_or_store(cache_id, key, store_fun)

Specs:

Dirty equivalent of get_or_store/3.

Source
dirty_insert_new(cache_id, key, value)

Specs:

Dirty equivalent of insert_new/3.

Source
dirty_put(cache_id, key, value)

Specs:

Dirty equivalent of put/3.

Source
dirty_update(cache_id, key, update_fun)

Specs:

Dirty equivalent of update/3.

Source
dirty_update_existing(cache_id, key, update_fun)

Specs:

  • dirty_update_existing(t, key, update_fun) :: :ok | {:error, :not_existing} | {:error, any}

Dirty equivalent of update_existing/3.

Source
ets(cache_id)

Specs:

  • ets(t) :: :ets.tab

Returns the ets table managed by the cache.

Source
get(cache_id, key)

Specs:

Reads the item from the cache.

A read is always “dirty”, meaning it doesn’t block while someone is updating the item under the same key. A read doesn’t expire TTL of the item, unless touch_on_read option is set while starting the cache.

Source
get_or_store(cache_id, key, store_fun)

Specs:

Retrieves the item from the cache, or inserts the new item.

If the item exists in the cache, it is retrieved. Otherwise, the lambda function is executed and its result is stored under the given key.

Note: if the item is already in the cache, this function amounts to a simple get without any locking, so you can expect it to be fairly fast.

Source
insert_new(cache_id, key, value)

Specs:

Inserts the item into the cache unless it exists.

Source
isolated(cache_id, key, timeout \\ nil, fun)

Specs:

  • isolated(t, key, nil | pos_integer, (() -> any)) :: any

Isolated execution over arbitrary lock in the cache.

You can do whatever you want in the function, not necessarily related to the cache. The return value is the result of the provided lambda.

This allows you to perform flexible isolation. If you use the key of your item as a key, then this operation will be exclusive to updates. This can be used e.g. to perform isolated reads:

# Process A:
ConCache.isolated(:my_cache, :my_item_key, fn() -> ... end)

# Process B:
ConCache.update(:my_cache, :my_item, fn(old_value) -> ... end)

These two operations are mutually exclusive.

Source
put(cache_id, key, value)

Specs:

Stores the item into the cache.

Source
start(options \\ [], gen_server_options \\ [])

Specs:

Starts the server.

See start_link/2 for more details.

Source
start_link(options \\ [], gen_server_options \\ [])

Specs:

Starts the server and creates an ETS table.

Options:

  • :set - An ETS table will be of the :set type (default).
  • :ordered_set - An ETS table will be of the :ordered_set type.
  • {:ttl_check, time_ms} - A check interval for TTL expiry. This value is by default nil and you need to provide a positive integer for TTL to work. See below for more details on inner workings of TTL.
  • {:ttl, time_ms} - The default time after which an item expires. When an item expires, it is removed from the cache. Updating the item extends its expiry time. By default, items never expire.
  • {:touch_on_read, true | false} - Controls whether read operation extends expiry of items. False by default.
  • {:callback, callback_fun} - If provided, this function is invoked after an item is inserted or updated, or before it is deleted.
  • {:acquire_lock_timeout, timeout_ms} - The time a client process waits for the lock. Default is 5000.

In addition, following ETS options are supported:

  • :named_table
  • :name
  • :heir
  • :write_concurrency
  • :read_concurrency

Choosing ttl_check time

When TTL is configured, the owner process works in discrete steps, doing cleanups every ttl_check_time milliseconds. This approach allows the owner process to do fairly small amount of work in each discrete step.

Assuming there’s no huge system overload, an item’s max lifetime is thus ttl_time + ttl_check_time [ms], after the last item’s update.

Thus, lower value of ttl_check time means more frequent purging which may reduce your memory consumption, but could also cause performance penalties. Higher values put less pressure on processing, but item expiry is less precise.

Source
touch(cache_id, key)

Specs:

  • touch(t, key) :: :ok

Manually touches the item to prolongate its expiry.

Source
try_isolated(cache_id, key, timeout \\ nil, on_success)

Specs:

  • try_isolated(t, key, nil | pos_integer, (() -> any)) :: {:error, :locked} | {:ok, any}

Similar to isolated/4 except it doesn’t wait for the lock to be available.

If the lock can be acquired immediately, it will be acquired and the function will be invoked. Otherwise, an error is returned immediately.

Source
update(cache_id, key, update_fun)

Specs:

Updates the item, or stores new item if it doesn’t exist.

The update_fun is invoked after the item is locked. Here, you can be certain that no other process will update this item, unless they are doing dirty updates or writing directly to the underlying ETS table.

The updater lambda must return one of the following:

  • {:ok, value} - causes the value to be stored into the table
Source
update_existing(cache_id, key, update_fun)

Specs:

  • update_existing(t, key, update_fun) :: :ok | {:error, :not_existing} | {:error, any}

Updates the item only if it exists. Otherwise works just like update/3.

Source