SuperCache.KeyValue (SuperCache v1.3.0)

Copy Markdown View Source

In-memory key-value namespaces backed by SuperCache ETS partitions.

Works transparently in both local and distributed modes — the mode is determined by the :cluster option passed to SuperCache.start!/1.

Multiple independent namespaces coexist using different kv_name values.

ETS table type support

KeyValue adapts its behavior based on the configured :table_type:

Operation:set / :ordered_set:bag / :duplicate_bag
add/3Atomic upsert (update or insert)Insert (duplicates allowed)
get/3Single value or defaultMost recent value or default
get_all/3List with at most one elementAll values for the key
update/3Atomic via update_elementDelete-all + insert (not atomic)
update/4Best-effort read-modify-writeBest-effort read-modify-write
increment/4Atomic via update_counterNot supported (raises)
replace/3Same as update/3Delete-all + insert

Atomic operations

  • update/3 — atomically set a value using :ets.update_element/3,4 (:set/:ordered_set only). If the key does not exist, a new record is inserted (upsert semantics).
  • increment/4 — atomically increment a counter using :ets.update_counter/3,4 (:set/:ordered_set only).
  • add/3 on :set/:ordered_set tables uses update_element with a default for true atomic upsert semantics in :async/:sync replication mode.

Read modes (distributed)

Pass read_mode: :primary or read_mode: :quorum when you need to read your own writes from any node.

Example

alias SuperCache.KeyValue

# Works identically in local and distributed mode
KeyValue.add("session", :user_id, 42)
KeyValue.get("session", :user_id)       # => 42
KeyValue.keys("session")                # => [:user_id]
KeyValue.remove("session", :user_id)
KeyValue.remove_all("session")

# Atomic operations (:set / :ordered_set tables)
KeyValue.update("counters", :hits, 1)           # => :ok
KeyValue.increment("counters", :hits, 0, 1)     # => 2

# Bag table support
KeyValue.get_all("tags", :elixir)               # => [1, 2]  (all values)
KeyValue.replace("tags", :elixir, 3)            # => :ok     (replace all)

Summary

Functions

Add or update a key-value pair.

Add multiple key-value pairs in a single batch operation.

Get the value for key, returning default if not found.

Get all values for key as a list.

Atomically increment a counter field.

Remove multiple keys in a single batch operation.

Replace all values for key with a single value.

Atomically set the value for key (upsert semantics).

Update the value for key using a function.

Functions

add(kv_name, key, value)

@spec add(any(), any(), any()) :: true

Add or update a key-value pair.

For :set/:ordered_set tables, this is an atomic upsert — if the key exists, the value is updated in-place via :ets.update_element; if not, a new record is inserted.

For :bag/:duplicate_bag tables, this inserts a new record. Duplicate keys are allowed. Use replace/3 to atomically replace all values for a key.

Returns true.

add_batch(kv_name, pairs)

@spec add_batch(any(), [{any(), any()}]) :: :ok

Add multiple key-value pairs in a single batch operation.

Groups entries by partition and sends each group in a single :erpc call in distributed mode, dramatically reducing network overhead.

Example

KeyValue.add_batch("session", [
  {:user_1, %{name: "Alice"}},
  {:user_2, %{name: "Bob"}}
])

count(kv_name, opts \\ [])

@spec count(
  any(),
  keyword()
) :: non_neg_integer()

get(kv_name, key, default \\ nil, opts \\ [])

@spec get(any(), any(), any(), keyword()) :: any()

Get the value for key, returning default if not found.

For :set/:ordered_set tables, returns the single value or default.

For :bag/:duplicate_bag tables, returns the most recently inserted value. Use get_all/3 to retrieve all values for a key.

get_all(kv_name, key, opts \\ [])

@spec get_all(any(), any(), keyword()) :: [any()]

Get all values for key as a list.

Useful for :bag/:duplicate_bag tables where multiple records can share the same key. For :set/:ordered_set tables, returns a list with at most one element.

increment(kv_name, key, default \\ 0, step \\ 1)

@spec increment(any(), any(), number(), number()) :: number()

Atomically increment a counter field.

The value at key must be a number. If the key doesn't exist, default is used as the initial value before incrementing by step.

Only supported for :set/:ordered_set tables. Raises ArgumentError for :bag/:duplicate_bag tables — use :set or :ordered_set for atomic counter operations.

Returns the new counter value.

keys(kv_name, opts \\ [])

@spec keys(
  any(),
  keyword()
) :: [any()]

remove(kv_name, key)

@spec remove(any(), any()) :: :ok

remove_all(kv_name)

@spec remove_all(any()) :: :ok

remove_batch(kv_name, keys)

@spec remove_batch(any(), [any()]) :: :ok

Remove multiple keys in a single batch operation.

Groups entries by partition and sends each group in a single :erpc call in distributed mode.

Example

KeyValue.remove_batch("session", [:user_1, :user_2])

replace(kv_name, key, value)

@spec replace(any(), any(), any()) :: :ok

Replace all values for key with a single value.

For :bag/:duplicate_bag tables, this deletes all existing records for the key and inserts a single new record. For :set/:ordered_set tables, this is equivalent to update/3 (atomic upsert).

Note: For :bag/:duplicate_bag tables, this operation is not atomic — a concurrent reader may observe the key as missing between the delete and the insert.

Returns :ok.

to_list(kv_name, opts \\ [])

@spec to_list(
  any(),
  keyword()
) :: [{any(), any()}]

update(kv_name, key, value)

@spec update(any(), any(), any()) :: :ok

Atomically set the value for key (upsert semantics).

For :set/:ordered_set tables, uses :ets.update_element/3,4 which is guaranteed atomic at the ETS level. If the key doesn't exist, a new record is inserted.

For :bag/:duplicate_bag tables, deletes all existing records for the key and inserts a new one. This is not atomic — a concurrent reader may observe the key as missing between the delete and the insert. Prefer :set or :ordered_set tables when atomic updates are required.

Returns :ok.

update(kv_name, key, default, fun)

@spec update(any(), any(), any(), (any() -> any())) :: any()

Update the value for key using a function.

fun receives the current value (or default if the key doesn't exist) and must return the new value.

Warning: This is a read-modify-write operation and is not atomic. A concurrent writer may modify the value between the read and the write, causing a lost update. For atomic value updates, use update/3. For atomic counter increments, use increment/4.

In distributed mode, fun is serialized and sent to the primary node via :erpc. The function must not capture node-specific resources (PIDs, etc.).

Returns the new value.

values(kv_name, opts \\ [])

@spec values(
  any(),
  keyword()
) :: [any()]