FerricStore (ferricstore v0.3.2)

Copy Markdown View Source

Module-based cache instances for FerricStore.

Each module that calls use FerricStore gets its own fully isolated cache instance with its own shards, ETS tables, Raft system, and config.

Usage

defmodule MyApp.Cache do
  use FerricStore,
    data_dir: "/data/cache",
    shard_count: 4,
    max_memory: "1GB"
end

# In your supervision tree:
children = [MyApp.Cache]

# Then use it:
MyApp.Cache.set("key", "value")
{:ok, "value"} = MyApp.Cache.get("key")

Multiple instances

defmodule MyApp.Sessions do
  use FerricStore,
    data_dir: "/data/sessions",
    shard_count: 2
end

MyApp.Cache.set("page:home", html)
MyApp.Sessions.set("sess:abc", session_data)

Options

  • :data_dir — base directory for Bitcask data files (required)
  • :shard_count — number of shards (default: 4)
  • :max_memory_bytes — maximum memory budget (default: 1GB)
  • :keydir_max_ram — maximum ETS keydir memory (default: 256MB)
  • :eviction_policy:volatile_lfu | :allkeys_lfu | :noeviction (default: :volatile_lfu)

  • :hot_cache_max_value_size — max value size for ETS caching (default: 65536)
  • :read_sample_rate — LFU sampling rate (default: 100)

Summary

Functions

Appends suffix to the string value stored at key.

Blocks until FerricStore is fully ready to serve requests.

Batch GET: takes a list of keys, returns a list of values (nil for missing).

Batch SET: takes a list of {key, value} pairs, returns a list of results.

Adds an element to the Bloom filter at key, auto-creating if needed.

Returns the approximate number of unique elements added to the Bloom filter at key.

Checks if an element may exist in the Bloom filter at key.

Returns metadata about the Bloom filter at key (capacity, error rate, size, etc.).

Adds multiple elements to the Bloom filter at key.

Checks if multiple elements may exist in the Bloom filter at key.

Creates a Bloom filter with specific error rate and capacity.

Counts the number of set bits (1s) in the string value stored at key.

Performs a bitwise operation between strings stored at source_keys and stores the result in dest_key.

Finds the first bit set to bit_value (0 or 1) in the string at key.

Performs an atomic compare-and-swap (optimistic locking) on key.

Adds an element to the Cuckoo filter at key, auto-creating if needed.

Adds an element to the Cuckoo filter only if it is not already present.

Returns the approximate number of times an element was added to the Cuckoo filter.

Deletes one occurrence of an element from the Cuckoo filter at key.

Checks if an element may exist in the Cuckoo filter at key.

Returns metadata about the Cuckoo filter at key (size, bucket count, etc.).

Checks multiple elements against the Cuckoo filter at key in a single call.

Creates a Cuckoo filter with the specified capacity.

Increments the count for one or more elements in the Count-Min Sketch.

Returns metadata about the Count-Min Sketch at key (width, depth, total count).

Creates a Count-Min Sketch with the given width and depth dimensions.

Creates a Count-Min Sketch with a target error rate and over-estimation probability.

Queries the estimated frequency count for one or more elements in the Count-Min Sketch.

Copies the value (and its TTL) from source to destination.

Returns the total number of user-visible keys in the store.

Decrements the integer value stored at key by 1.

Decrements the integer value stored at key by amount.

Deletes one or more keys from the store.

Echoes back the given message, useful for connection testing.

Checks whether key exists in the store and has not expired.

Sets a TTL (in milliseconds) on an existing key.

Sets the key to expire at the given absolute Unix timestamp (in seconds).

Returns the absolute Unix timestamp (in seconds) at which key will expire.

Extends the TTL of a lock on key, but only if it is currently held by owner.

Cache-aside pattern with stampede (thundering herd) protection.

Stores the computed value for a fetch_or_compute/2 cache miss and unblocks waiters.

Deletes all keys from the store.

Deletes all keys from the store.

Adds geospatial members (longitude, latitude, name) to the geo index at key.

Returns the distance between two geo members.

Returns geohash strings for the specified members.

Returns the longitude/latitude positions for the specified members.

Gets the value stored at key.

Returns the bit value at offset in the string value stored at key.

Atomically gets the value of key and deletes it.

Gets the value of key and optionally updates its expiry.

Returns a substring of the string stored at key between byte offsets start and stop (inclusive).

Atomically sets key to value and returns the previous value.

Deletes one or more fields from the hash stored at key.

Returns the current health status without blocking.

Returns whether field exists in the hash stored at key.

Gets the value of a single field from the hash stored at key.

Gets all fields and values from the hash stored at key.

Increments the integer value of field in the hash at key by amount.

Increments the float value of field in the hash at key by amount.

Returns all field names from the hash stored at key.

Returns the number of fields in the hash stored at key.

Returns values for the specified fields from the hash at key.

Returns one or more random field names from the hash at key.

Sets one or more fields in the hash stored at key.

Sets field in the hash at key only if the field does not already exist.

Returns the string length of the value for field in the hash at key.

Returns all field values from the hash stored at key.

Increments the integer value stored at key by 1.

Increments the integer value stored at key by amount.

Increments the numeric value stored at key by a floating-point amount.

Appends one or more JSON values to the array at path in the document at key.

Returns the length of the JSON array at path in the document at key.

Deletes the value at path from the JSON document at key.

Gets the JSON value at path from the document stored at key.

Atomically increments a numeric value at path in the JSON document at key.

Returns the keys of the JSON object at path in the document at key.

Returns the number of keys in the JSON object at path in the document at key.

Sets a JSON value at path in the document stored at key.

Returns the length of the JSON string at path in the document at key.

Returns the JSON type of the value at path in the document at key.

Returns all keys matching pattern (glob-style).

Returns the element at index in the list stored at key.

Inserts element before or after pivot in the list at key.

Returns the length of the list stored at key.

Atomically moves an element from one list to another.

Acquires a distributed mutex lock on key with the given owner identity and TTL.

Pops one or more elements from the left (head) of the list stored at key.

Finds the position of element in the list at key.

Pushes one or more elements to the left (head) of the list stored at key.

Returns elements from the list stored at key within the range start..stop.

Removes occurrences of element from the list at key.

Sets the element at index in the list stored at key.

Gets values for multiple keys in a single call.

Sets multiple key-value pairs in a single call.

Sets multiple key-value pairs only if none of the given keys already exist.

Executes a sequence of commands atomically as a transaction.

Packed binary batch GET — minimal distribution overhead.

Removes the TTL from key, making it persist indefinitely.

Sets a TTL in milliseconds on an existing key.

Sets the key to expire at the given absolute Unix timestamp (in milliseconds).

Returns the absolute Unix timestamp (in milliseconds) at which key will expire.

Adds elements to the HyperLogLog at key for approximate cardinality counting.

Returns the approximate number of unique elements across one or more HyperLogLogs.

Merges multiple HyperLogLog keys into dest_key.

Health check that returns {:ok, "PONG"}.

Batches multiple commands into a single group-commit entry.

Sets key to value with a TTL in milliseconds.

Returns the remaining time-to-live in milliseconds for key.

Returns a random key from the store, or {:ok, nil} if the store is empty.

Records count events against the sliding-window rate limiter at key.

Returns true if FerricStore is ready to serve requests.

Renames source to destination, overwriting destination if it exists.

Renames source to destination only if destination does not already exist.

Pops one or more elements from the right (tail) of the list stored at key.

Pushes one or more elements to the right (tail) of the list stored at key.

Adds one or more members to the set stored at key.

Returns the number of members in the set stored at key (the set cardinality).

Returns the set difference: members in the first set that are not in any of the other sets.

Computes the set difference of the given keys and stores the result in destination.

Sets key to value with optional TTL and condition flags.

Sets or clears the bit at offset in the string value stored at key.

Sets key to value with a TTL in seconds.

Sets key to value only if the key does not already exist.

Overwrites part of the string stored at key starting at byte offset.

Gracefully shuts down FerricStore, flushing all pending data to disk.

Returns the set intersection: members common to all given sets.

Returns the cardinality of the intersection of all given sets.

Computes the set intersection of the given keys and stores the result in destination.

Checks whether member is a member of the set stored at key.

Returns all members of the set stored at key.

Returns the membership status of multiple members in the set at key.

Removes and returns one or more random members from the set at key.

Returns one or more random members from the set at key without removing them.

Removes one or more members from the set stored at key.

Returns the byte length of the string value stored at key.

Returns the set union: all unique members across all given sets.

Computes the set union of the given keys and stores the result in destination.

Adds one or more numeric observations to the T-Digest at key.

Estimates the value at each given rank (0-based position in sorted order).

Estimates the value at each given reverse rank (0 = largest, 1 = second largest, etc.).

Estimates the cumulative distribution function (CDF) at the given values.

Creates a T-Digest structure at key for estimating quantiles and percentiles.

Returns metadata about the T-Digest at key (compression, total observations, etc.).

Returns the maximum value observed in the T-Digest at key.

Returns the minimum value observed in the T-Digest at key.

Estimates the values at the given quantile points (0.0 to 1.0).

Estimates the rank (number of observations less than or equal to) for each value.

Resets the T-Digest at key, discarding all observations.

Estimates the reverse rank (number of observations greater than) for each value.

Computes the trimmed mean of values between quantile bounds lo and hi.

Adds one or more elements to the Top-K tracker, updating frequency counts.

Returns metadata about the Top-K tracker at key (k, width, depth, decay).

Returns the current Top-K elements, ordered by estimated frequency (descending).

Checks whether elements are currently in the Top-K set.

Creates a Top-K tracker that maintains the k most frequent elements.

Returns the remaining time-to-live in milliseconds for key.

Returns the data type of the value stored at key.

Releases the lock on key, but only if it is currently held by owner.

Appends an entry to the stream at key with an auto-generated ID.

Returns the number of entries in the stream at key.

Returns entries from the stream at key in forward (oldest-first) order between start and stop.

Returns entries from the stream at key in reverse (newest-first) order between stop and start.

Trims the stream at key to a maximum number of entries, evicting the oldest.

Adds members with scores to the sorted set stored at key.

Returns the number of members in the sorted set stored at key.

Counts members in the sorted set at key with scores between min and max.

Increments the score of member in the sorted set at key by increment.

Returns scores for multiple members in the sorted set at key.

Removes and returns up to count members with the highest scores.

Removes and returns up to count members with the lowest scores.

Returns one or more random members from the sorted set at key.

Returns members in the sorted set stored at key within the rank range start..stop.

Returns members with scores between min and max (inclusive by default).

Returns the rank of member in the sorted set at key (ascending score order).

Removes one or more members from the sorted set stored at key.

Returns the reverse rank of member in the sorted set at key (descending score order).

Returns the score of member in the sorted set stored at key.

Types

cas_opts()

@type cas_opts() :: [{:ttl, non_neg_integer()}]

fetch_or_compute_opts()

@type fetch_or_compute_opts() :: [ttl: pos_integer(), hint: binary()]

get_opts()

@type get_opts() :: [{:cache, atom()}]

key()

@type key() :: binary()

set_opts()

@type set_opts() :: [
  ttl: non_neg_integer(),
  exat: pos_integer(),
  pxat: pos_integer(),
  nx: boolean(),
  xx: boolean(),
  get: boolean(),
  keepttl: boolean(),
  cache: atom()
]

value()

@type value() :: binary()

zrange_opts()

@type zrange_opts() :: [{:withscores, boolean()}]

Functions

append(key, suffix)

@spec append(key(), binary()) :: {:ok, non_neg_integer()}

Appends suffix to the string value stored at key.

If the key does not exist, it is created with suffix as its value. Returns the byte length of the string after the append.

Examples

iex> FerricStore.set("log:request:42", "GET /api")
:ok
iex> FerricStore.append("log:request:42", " 200 OK")
{:ok, 15}

iex> FerricStore.append("new:key", "hello")
{:ok, 5}

await_ready(opts \\ [])

@spec await_ready(keyword()) :: :ok

Blocks until FerricStore is fully ready to serve requests.

Polls Health.check/0 until all shards are alive and all Raft leaders are elected. Returns :ok when ready, raises on timeout.

Call this in your application's start/2 after FerricStore is in your supervision tree, or in test setup, to ensure writes won't fail.

Options

  • :timeout - max milliseconds to wait (default: 30_000)
  • :interval - polling interval in ms (default: 100)

Examples

# In your Application.start/2:
def start(_type, _args) do
  children = [
    {FerricStore, []},
    MyApp.Repo,
    MyAppWeb.Endpoint
  ]
  opts = [strategy: :one_for_one, name: MyApp.Supervisor]
  {:ok, pid} = Supervisor.start_link(children, opts)

  FerricStore.await_ready()
  {:ok, pid}
end

# With custom timeout:
FerricStore.await_ready(timeout: 60_000)

batch_get(keys)

@spec batch_get([binary()]) :: [binary() | nil]

Batch GET: takes a list of keys, returns a list of values (nil for missing).

Goes directly to Router.batch_get — single HLC timestamp, zero GenServer, zero Pipe struct overhead. Designed for erpc callers.

batch_set(kv_pairs)

@spec batch_set([{binary(), binary()}]) :: [:ok | {:error, binary()}]

Batch SET: takes a list of {key, value} pairs, returns a list of results.

Routes through Router.batch_quorum_put or batch_async_put based on namespace durability. Designed for erpc callers.

bf_add(key, element)

@spec bf_add(key(), binary()) :: {:ok, 0 | 1}

Adds an element to the Bloom filter at key, auto-creating if needed.

Returns

  • {:ok, 1} if the element was added.
  • {:ok, 0} if the element was already present.

Examples

{:ok, 1} = FerricStore.bf_add("filter", "hello")

bf_card(key)

@spec bf_card(key()) :: {:ok, non_neg_integer()}

Returns the approximate number of unique elements added to the Bloom filter at key.

Returns

  • {:ok, count} on success.

Examples

iex> FerricStore.bf_card("emails:seen")
{:ok, 42}

bf_exists(key, element)

@spec bf_exists(key(), binary()) :: {:ok, 0 | 1}

Checks if an element may exist in the Bloom filter at key.

Returns

  • {:ok, 1} if the element may exist.
  • {:ok, 0} if the element definitely does not exist.

bf_info(key)

@spec bf_info(key()) :: {:ok, list()} | {:error, binary()}

Returns metadata about the Bloom filter at key (capacity, error rate, size, etc.).

Returns

  • {:ok, info_list} - flat key-value list of filter properties.
  • {:error, reason} if the filter does not exist.

Examples

iex> FerricStore.bf_info("emails:seen")
{:ok, ["Capacity", 100000, "Size", 120048, "Number of filters", 1, "Number of items inserted", 42, "Expansion rate", 2]}

bf_madd(key, elements)

@spec bf_madd(key(), [binary()]) :: {:ok, [0 | 1]}

Adds multiple elements to the Bloom filter at key.

Returns

  • {:ok, [0 | 1, ...]} for each element.

bf_mexists(key, elements)

@spec bf_mexists(key(), [binary()]) :: {:ok, [0 | 1]}

Checks if multiple elements may exist in the Bloom filter at key.

Returns

  • {:ok, [0 | 1, ...]} for each element.

bf_reserve(key, error_rate, capacity)

@spec bf_reserve(key(), float(), pos_integer()) :: :ok | {:error, binary()}

Creates a Bloom filter with specific error rate and capacity.

Examples

:ok = FerricStore.bf_reserve("filter", 0.01, 1000)

bitcount(key, opts \\ [])

@spec bitcount(
  key(),
  keyword()
) :: {:ok, non_neg_integer()}

Counts the number of set bits (1s) in the string value stored at key.

Options

  • :start - Start byte offset (default: 0).
  • :stop - Stop byte offset (default: -1, meaning end of string).

Returns

  • {:ok, count} on success.

Examples

{:ok, count} = FerricStore.bitcount("key")

bitop(op, dest_key, source_keys)

@spec bitop(atom(), key(), [key()]) :: {:ok, non_neg_integer()}

Performs a bitwise operation between strings stored at source_keys and stores the result in dest_key.

Parameters

  • op - :and, :or, :xor, or :not
  • dest_key - Destination key.
  • source_keys - List of source keys.

Returns

  • {:ok, byte_length} - Length of the result string.

Examples

{:ok, 3} = FerricStore.bitop(:and, "dest", ["key1", "key2"])

bitpos(key, bit_value, opts \\ [])

@spec bitpos(key(), 0 | 1, keyword()) :: {:ok, integer()}

Finds the first bit set to bit_value (0 or 1) in the string at key.

Options

  • :start - Start byte offset.
  • :stop - Stop byte offset.

Returns

  • {:ok, position} - Bit position, or -1 if not found within a bounded range.

Examples

{:ok, 8} = FerricStore.bitpos("key", 1)

cas(key, expected, new_value, opts \\ [])

@spec cas(key(), binary(), binary(), cas_opts()) :: {:ok, true | false | nil}

Performs an atomic compare-and-swap (optimistic locking) on key.

If the current value of key equals expected, it is atomically replaced with new_value. This is the building block for lock-free concurrent updates -- read the current value, compute the new value, then CAS. If another writer changed the value in between, CAS returns false and you retry.

Options

  • :ttl - Time-to-live in milliseconds for the new value. When omitted, the existing TTL is preserved.

Returns

  • {:ok, true} if the swap was performed.
  • {:ok, false} if the current value did not match expected (retry needed).
  • {:ok, nil} if the key does not exist.

Examples

iex> FerricStore.set("inventory:item:99", "10")
:ok
iex> FerricStore.cas("inventory:item:99", "10", "9")
{:ok, true}
iex> FerricStore.cas("inventory:item:99", "10", "8")
{:ok, false}

cf_add(key, element)

@spec cf_add(key(), binary()) :: {:ok, 0 | 1} | {:error, binary()}

Adds an element to the Cuckoo filter at key, auto-creating if needed.

Unlike Bloom filters, duplicate insertions increase the count for the element.

Returns

  • {:ok, 1} on success.
  • {:error, reason} if the filter is full and cannot accommodate the element.

Examples

iex> FerricStore.cf_add("sessions:active", "sess_abc123")
{:ok, 1}

cf_addnx(key, element)

@spec cf_addnx(key(), binary()) :: {:ok, 0 | 1} | {:error, binary()}

Adds an element to the Cuckoo filter only if it is not already present.

Returns

  • {:ok, 1} if the element was newly added.
  • {:ok, 0} if the element already exists.
  • {:error, reason} if the filter is full.

Examples

iex> FerricStore.cf_addnx("sessions:active", "sess_abc123")
{:ok, 1}

iex> FerricStore.cf_addnx("sessions:active", "sess_abc123")
{:ok, 0}

cf_count(key, element)

@spec cf_count(key(), binary()) :: {:ok, non_neg_integer()}

Returns the approximate number of times an element was added to the Cuckoo filter.

Returns

  • {:ok, count} - estimated insertion count for the element.

Examples

iex> FerricStore.cf_count("sessions:active", "sess_abc123")
{:ok, 1}

cf_del(key, element)

@spec cf_del(key(), binary()) :: {:ok, 0 | 1}

Deletes one occurrence of an element from the Cuckoo filter at key.

This is the key advantage of Cuckoo filters over Bloom filters -- elements can be removed. Only deletes one occurrence if the element was added multiple times.

Returns

  • {:ok, 1} if the element was deleted.
  • {:ok, 0} if the element was not found.

Examples

iex> FerricStore.cf_del("sessions:active", "sess_abc123")
{:ok, 1}

cf_exists(key, element)

@spec cf_exists(key(), binary()) :: {:ok, 0 | 1}

Checks if an element may exist in the Cuckoo filter at key.

Returns

  • {:ok, 1} if the element probably exists.
  • {:ok, 0} if the element definitely does not exist.

Examples

iex> FerricStore.cf_exists("sessions:active", "sess_abc123")
{:ok, 1}

cf_info(key)

@spec cf_info(key()) :: {:ok, list()} | {:error, binary()}

Returns metadata about the Cuckoo filter at key (size, bucket count, etc.).

Returns

  • {:ok, info_list} - flat key-value list of filter properties.
  • {:error, reason} if the filter does not exist.

Examples

iex> FerricStore.cf_info("sessions:active")
{:ok, ["Size", 1024, "Number of buckets", 512, "Number of filters", 1, "Number of items inserted", 3, "Number of items deleted", 0, "Bucket size", 2, "Expansion rate", 1, "Max iterations", 20]}

cf_mexists(key, elements)

@spec cf_mexists(key(), [binary()]) :: {:ok, [0 | 1]}

Checks multiple elements against the Cuckoo filter at key in a single call.

Returns

  • {:ok, [0 | 1, ...]} - 1 for probably present, 0 for definitely absent, one per element.

Examples

iex> FerricStore.cf_mexists("sessions:active", ["sess_abc123", "sess_unknown"])
{:ok, [1, 0]}

cf_reserve(key, capacity)

@spec cf_reserve(key(), pos_integer()) :: :ok | {:error, binary()}

Creates a Cuckoo filter with the specified capacity.

A Cuckoo filter is similar to a Bloom filter but supports deletion and counting. Use it when you need probabilistic membership checks with the ability to remove items later (e.g., tracking active sessions that can be revoked).

Parameters

  • key - the Cuckoo filter key
  • capacity - expected number of elements

Examples

iex> FerricStore.cf_reserve("sessions:active", 50_000)
:ok

cms_incrby(key, pairs)

@spec cms_incrby(key(), [{binary(), pos_integer()}]) ::
  {:ok, [non_neg_integer()]} | {:error, binary()}

Increments the count for one or more elements in the Count-Min Sketch.

Parameters

  • key - the CMS key
  • pairs - list of {element, increment} tuples

Returns

  • {:ok, [new_count, ...]} - estimated count after increment, one per element.
  • {:error, reason} if the sketch does not exist.

Examples

iex> FerricStore.cms_incrby("page:views", [{"homepage", 1}, {"about", 3}])
{:ok, [1, 3]}

cms_info(key)

@spec cms_info(key()) :: {:ok, list()} | {:error, binary()}

Returns metadata about the Count-Min Sketch at key (width, depth, total count).

Returns

  • {:ok, info_list} - flat key-value list of sketch properties.
  • {:error, reason} if the sketch does not exist.

Examples

iex> FerricStore.cms_info("page:views")
{:ok, ["width", 2000, "depth", 5, "count", 49]}

cms_initbydim(key, width, depth)

@spec cms_initbydim(key(), pos_integer(), pos_integer()) :: :ok | {:error, binary()}

Creates a Count-Min Sketch with the given width and depth dimensions.

A Count-Min Sketch is a probabilistic structure for approximate frequency counting. It uses sub-linear space and answers "how many times has X been seen?" with bounded over-estimation. Ideal for view counts, click tracking, and frequency analysis where exact counts are not required.

Parameters

  • key - the CMS key
  • width - number of counters per hash function (larger = more accurate)
  • depth - number of hash functions (larger = lower error probability)

Examples

iex> FerricStore.cms_initbydim("page:views", 2000, 5)
:ok

cms_initbyprob(key, error, probability)

@spec cms_initbyprob(key(), float(), float()) :: :ok | {:error, binary()}

Creates a Count-Min Sketch with a target error rate and over-estimation probability.

The sketch dimensions (width/depth) are computed automatically from the error bounds.

Parameters

  • key - the CMS key
  • error - acceptable error rate as a fraction (e.g. 0.001 for 0.1%)
  • probability - probability of exceeding the error rate (e.g. 0.01 for 1%)

Examples

iex> FerricStore.cms_initbyprob("click:tracking", 0.001, 0.01)
:ok

cms_query(key, elements)

@spec cms_query(key(), [binary()]) :: {:ok, [non_neg_integer()]} | {:error, binary()}

Queries the estimated frequency count for one or more elements in the Count-Min Sketch.

Counts may be over-estimated but never under-estimated.

Returns

  • {:ok, [count, ...]} - estimated count per element.
  • {:error, reason} if the sketch does not exist.

Examples

iex> FerricStore.cms_query("page:views", ["homepage", "about", "unknown"])
{:ok, [42, 7, 0]}

copy(source, destination, opts \\ [])

@spec copy(key(), key(), keyword()) :: {:ok, true} | {:error, binary()}

Copies the value (and its TTL) from source to destination.

By default, returns an error if the destination already exists. Pass :replace to overwrite.

Options

  • :replace - When true, overwrites destination if it already exists.

Examples

iex> FerricStore.set("user:42:name", "alice")
:ok
iex> FerricStore.copy("user:42:name", "user:42:name:backup")
{:ok, true}

iex> FerricStore.copy("user:42:name", "user:42:name:backup")
{:error, "ERR target key already exists"}

iex> FerricStore.copy("user:42:name", "user:42:name:backup", replace: true)
{:ok, true}

iex> FerricStore.copy("nonexistent", "dst")
{:error, "ERR no such key"}

dbsize()

@spec dbsize() :: {:ok, non_neg_integer()}

Returns the total number of user-visible keys in the store.

Internal compound keys (used by hashes, lists, sets, and sorted sets) are excluded from the count.

Examples

iex> FerricStore.set("key:a", "1")
:ok
iex> FerricStore.set("key:b", "2")
:ok
iex> {:ok, count} = FerricStore.dbsize()
iex> count >= 2
true

decr(key)

@spec decr(key()) :: {:ok, integer()} | {:error, binary()}

Decrements the integer value stored at key by 1.

If the key does not exist, it is initialized to 0 before decrementing, resulting in a value of -1. Returns {:error, reason} if the stored value cannot be parsed as an integer.

Examples

iex> FerricStore.decr("rate_limit:user:42")
{:ok, -1}

iex> FerricStore.set("stock:item:99", "10")
:ok
iex> FerricStore.decr("stock:item:99")
{:ok, 9}

decr_by(key, amount)

@spec decr_by(key(), integer()) :: {:ok, integer()} | {:error, binary()}

Decrements the integer value stored at key by amount.

If the key does not exist, it is initialized to 0 before decrementing. Returns {:error, reason} if the stored value is not a valid integer.

Examples

iex> FerricStore.set("stock:item:99", "100")
:ok
iex> FerricStore.decr_by("stock:item:99", 10)
{:ok, 90}

iex> FerricStore.decr_by("new_counter", 5)
{:ok, -5}

del(key)

@spec del(key() | [key()]) :: {:ok, non_neg_integer()} | {:error, binary()}

Deletes one or more keys from the store.

Accepts a single key or a list of keys. Returns {:ok, count} where count is the number of keys that were actually deleted.

Examples

iex> FerricStore.set("k1", "v1")
iex> FerricStore.del("k1")
{:ok, 1}

iex> FerricStore.del("nonexistent")
{:ok, 0}

iex> FerricStore.set("a", "1")
iex> FerricStore.set("b", "2")
iex> FerricStore.del(["a", "b", "c"])
{:ok, 2}

echo(message)

@spec echo(binary()) :: {:ok, binary()}

Echoes back the given message, useful for connection testing.

Examples

iex> FerricStore.echo("hello")
{:ok, "hello"}

exists(key)

@spec exists(key()) :: boolean()

Checks whether key exists in the store and has not expired.

Examples

iex> FerricStore.set("user:42:name", "alice")
:ok
iex> FerricStore.exists("user:42:name")
true

iex> FerricStore.exists("nonexistent:key")
false

expire(key, ttl_ms)

@spec expire(key(), non_neg_integer()) :: {:ok, boolean()}

Sets a TTL (in milliseconds) on an existing key.

The key will be automatically deleted after ttl_ms milliseconds have elapsed. Returns {:ok, false} if the key does not exist.

Examples

iex> FerricStore.set("session:abc", "data")
:ok
iex> FerricStore.expire("session:abc", :timer.minutes(30))
{:ok, true}

iex> FerricStore.expire("nonexistent:key", 5_000)
{:ok, false}

expireat(key, unix_ts_seconds)

@spec expireat(key(), non_neg_integer()) :: {:ok, boolean()}

Sets the key to expire at the given absolute Unix timestamp (in seconds).

The key will be automatically deleted when the system clock reaches the specified timestamp. Returns {:ok, false} if the key does not exist.

Examples

iex> FerricStore.set("event:promo", "active")
:ok
iex> FerricStore.expireat("event:promo", 1_700_000_000)
{:ok, true}

iex> FerricStore.expireat("nonexistent:key", 1_700_000_000)
{:ok, false}

expiretime(key)

@spec expiretime(key()) :: {:ok, integer()}

Returns the absolute Unix timestamp (in seconds) at which key will expire.

Returns {:ok, -1} if the key exists but has no associated expiry, and {:ok, -2} if the key does not exist.

Examples

iex> FerricStore.set("session:abc", "data", ttl: 60_000)
:ok
iex> {:ok, ts} = FerricStore.expiretime("session:abc")
iex> ts > 0
true

iex> FerricStore.set("permanent:key", "data")
:ok
iex> FerricStore.expiretime("permanent:key")
{:ok, -1}

iex> FerricStore.expiretime("nonexistent:key")
{:ok, -2}

extend(key, owner, ttl_ms)

@spec extend(key(), binary(), pos_integer()) :: {:ok, 1} | {:error, binary()}

Extends the TTL of a lock on key, but only if it is currently held by owner.

Call this periodically to prevent lock expiry while a long-running operation is still in progress.

Returns

  • {:ok, 1} if the TTL was extended.
  • {:error, reason} if the lock is not held by owner.

Examples

iex> FerricStore.extend("lock:order:123", "worker_abc", 30_000)
{:ok, 1}

fetch_or_compute(key, opts)

@spec fetch_or_compute(key(), fetch_or_compute_opts()) ::
  {:ok, {:hit, binary()} | {:compute, binary()}} | {:error, binary()}

Cache-aside pattern with stampede (thundering herd) protection.

Checks whether key has a cached value. If it does, returns {:ok, {:hit, value}}. If not, returns {:ok, {:compute, hint}} to indicate that the caller should compute the value and store it via fetch_or_compute_result/3.

Only one caller at a time receives {:compute, hint} for a given key -- all other concurrent callers block until the winner stores the computed value. This prevents N concurrent cache misses from triggering N identical expensive computations (the "stampede" problem).

Options

  • :ttl (required) - TTL in milliseconds for the cached value.
  • :hint - An opaque string passed back in {:compute, hint}. Defaults to "".

Returns

  • {:ok, {:hit, value}} if the value is cached.
  • {:ok, {:compute, hint}} if the caller should compute the value.
  • {:error, reason} on failure.

Examples

case FerricStore.fetch_or_compute("dashboard:stats:today", ttl: 30_000) do
  {:ok, {:hit, cached}} ->
    Jason.decode!(cached)

  {:ok, {:compute, _hint}} ->
    stats = DashboardService.compute_stats()
    encoded = Jason.encode!(stats)
    FerricStore.fetch_or_compute_result("dashboard:stats:today", encoded, ttl: 30_000)
    stats
end

fetch_or_compute_result(key, value, opts)

@spec fetch_or_compute_result(key(), binary(), keyword()) :: :ok | {:error, binary()}

Stores the computed value for a fetch_or_compute/2 cache miss and unblocks waiters.

Must be called after receiving {:ok, {:compute, hint}} from fetch_or_compute/2. Stores the value in the cache and wakes all concurrent callers that were blocked waiting for the computation to complete.

Options

  • :ttl (required) - TTL in milliseconds for the cached value.

Returns

  • :ok on success.

Examples

iex> FerricStore.fetch_or_compute_result("dashboard:stats:today", "cached_value", ttl: 30_000)
:ok

flushall()

@spec flushall() :: :ok

Deletes all keys from the store.

Alias for flushdb/0.

Examples

iex> FerricStore.flushall()
:ok

flushdb()

@spec flushdb() :: :ok

Deletes all keys from the store.

Returns

  • :ok

Examples

:ok = FerricStore.flushdb()

geoadd(key, members)

@spec geoadd(key(), [{number(), number(), binary()}]) ::
  {:ok, non_neg_integer()} | {:error, binary()}

Adds geospatial members (longitude, latitude, name) to the geo index at key.

Members are stored in a sorted set using geohash-encoded scores, enabling radius queries and distance calculations for location-based features.

Parameters

  • key - the geo index key
  • members - list of {longitude, latitude, name} tuples

Returns

  • {:ok, added_count} - number of new members added.
  • {:error, reason} on failure.

Examples

iex> FerricStore.geoadd("stores:nyc", [
...>   {-73.935242, 40.730610, "brooklyn_store"},
...>   {-74.0060, 40.7128, "manhattan_store"}
...> ])
{:ok, 2}

geodist(key, member1, member2, unit \\ "m")

@spec geodist(key(), binary(), binary(), binary()) ::
  {:ok, binary()} | {:error, binary()}

Returns the distance between two geo members.

Parameters

  • key - the geo index key
  • member1 - first member name
  • member2 - second member name
  • unit - distance unit: "m" (meters, default), "km", "mi", or "ft"

Returns

  • {:ok, distance_string} on success.
  • {:ok, nil} if either member does not exist.
  • {:error, reason} on failure.

Examples

iex> FerricStore.geodist("stores:nyc", "brooklyn_store", "manhattan_store", "km")
{:ok, "8.4567"}

geohash(key, members)

@spec geohash(key(), [binary()]) :: {:ok, list()} | {:error, binary()}

Returns geohash strings for the specified members.

Geohashes are base-32 encoded strings representing a geographic area, useful for proximity grouping and prefix-based spatial queries.

Returns

  • {:ok, [geohash | nil, ...]} - a geohash per member, or nil for missing members.

Examples

iex> FerricStore.geohash("stores:nyc", ["brooklyn_store", "manhattan_store"])
{:ok, ["dr5regy3zc0", "dr5regw3pp0"]}

geopos(key, members)

@spec geopos(key(), [binary()]) :: {:ok, list()} | {:error, binary()}

Returns the longitude/latitude positions for the specified members.

Returns

  • {:ok, [[longitude, latitude] | nil, ...]} - coordinates per member, or nil for missing members.

Examples

iex> FerricStore.geopos("stores:nyc", ["brooklyn_store"])
{:ok, [["-73.935242", "40.730610"]]}

get(key, opts \\ [])

@spec get(key(), get_opts()) :: {:ok, value() | nil}

Gets the value stored at key.

Returns {:ok, value} if the key exists and has not expired, or {:ok, nil} if the key does not exist or has expired.

Examples

iex> FerricStore.set("user:42:name", "alice")
:ok
iex> FerricStore.get("user:42:name")
{:ok, "alice"}

iex> FerricStore.get("nonexistent:key")
{:ok, nil}

getbit(key, offset)

@spec getbit(key(), non_neg_integer()) :: {:ok, 0 | 1}

Returns the bit value at offset in the string value stored at key.

Returns {:ok, 0} for nonexistent keys or out-of-range offsets.

Examples

{:ok, 1} = FerricStore.getbit("key", 7)

getdel(key)

@spec getdel(key()) :: {:ok, value() | nil}

Atomically gets the value of key and deletes it.

Returns {:ok, value} or {:ok, nil} if the key did not exist. Useful for consuming one-time tokens or dequeuing single values.

Examples

iex> FerricStore.set("otp:user:42", "839201")
:ok
iex> FerricStore.getdel("otp:user:42")
{:ok, "839201"}
iex> FerricStore.getdel("otp:user:42")
{:ok, nil}

getex(key, opts \\ [])

@spec getex(
  key(),
  keyword()
) :: {:ok, value() | nil}

Gets the value of key and optionally updates its expiry.

When called without options, behaves identically to get/2. Pass :ttl to refresh the expiry on access, or :persist to remove it.

Options

  • :ttl - New TTL in milliseconds to set on the key.
  • :persist - When true, removes any existing TTL, making the key persistent.

Examples

iex> FerricStore.set("session:abc", "data", ttl: 10_000)
:ok
iex> FerricStore.getex("session:abc", ttl: 60_000)
{:ok, "data"}

iex> FerricStore.getex("session:abc", persist: true)
{:ok, "data"}

iex> FerricStore.getex("nonexistent:key")
{:ok, nil}

getrange(key, start, stop)

@spec getrange(key(), integer(), integer()) :: {:ok, binary()}

Returns a substring of the string stored at key between byte offsets start and stop (inclusive).

Negative offsets count from the end of the string (-1 is the last byte). Returns {:ok, ""} if the key does not exist or the range is empty.

Examples

iex> FerricStore.set("greeting", "Hello, World!")
:ok
iex> FerricStore.getrange("greeting", 7, 11)
{:ok, "World"}

iex> FerricStore.getrange("greeting", -6, -1)
{:ok, "orld!"}

iex> FerricStore.getrange("nonexistent", 0, 10)
{:ok, ""}

getset(key, value)

@spec getset(key(), value()) :: {:ok, value() | nil}

Atomically sets key to value and returns the previous value.

Returns {:ok, old_value} or {:ok, nil} if the key did not previously exist. Useful for atomic swap patterns like rotating session tokens.

Examples

iex> FerricStore.set("session:token", "tok_abc")
:ok
iex> FerricStore.getset("session:token", "tok_xyz")
{:ok, "tok_abc"}

iex> FerricStore.getset("fresh:key", "first_value")
{:ok, nil}

hdel(key, fields)

@spec hdel(key(), [binary()]) :: {:ok, non_neg_integer()}

Deletes one or more fields from the hash stored at key.

Fields that do not exist are ignored. Returns the count of fields actually removed.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice", "age" => "30", "email" => "a@b.c"})
iex> FerricStore.hdel("user:42", ["age", "email"])
{:ok, 2}

iex> FerricStore.hdel("user:42", ["nonexistent"])
{:ok, 0}

health()

@spec health() :: Ferricstore.Health.health_result()

Returns the current health status without blocking.

Examples

iex> FerricStore.health()
%{status: :ok, shard_count: 4, shards: [...], uptime_seconds: 120}

hexists(key, field)

@spec hexists(key(), binary()) :: boolean()

Returns whether field exists in the hash stored at key.

Returns true if the field exists, false otherwise. Returns false if the key itself does not exist.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice"})
iex> FerricStore.hexists("user:42", "name")
true

iex> FerricStore.hexists("user:42", "missing")
false

iex> FerricStore.hexists("no_such_hash", "field")
false

hget(key, field)

@spec hget(key(), binary()) :: {:ok, binary() | nil}

Gets the value of a single field from the hash stored at key.

Returns {:ok, value} if the field exists, or {:ok, nil} if the field or the hash does not exist.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice", "age" => "30"})
iex> FerricStore.hget("user:42", "name")
{:ok, "alice"}

iex> FerricStore.hget("user:42", "nonexistent_field")
{:ok, nil}

iex> FerricStore.hget("no_such_hash", "field")
{:ok, nil}

hgetall(key)

@spec hgetall(key()) :: {:ok, %{required(binary()) => binary()}}

Gets all fields and values from the hash stored at key.

Returns {:ok, map} where map is a %{field => value} map. If the key does not exist, returns {:ok, %{}}.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice", "age" => "30"})
iex> FerricStore.hgetall("user:42")
{:ok, %{"name" => "alice", "age" => "30"}}

iex> FerricStore.hgetall("no_such_hash")
{:ok, %{}}

hincrby(key, field, amount)

@spec hincrby(key(), binary(), integer()) :: {:ok, integer()} | {:error, binary()}

Increments the integer value of field in the hash at key by amount.

If the field does not exist, it is created with 0 before incrementing. Returns {:error, reason} if the field value is not a valid integer.

Examples

iex> FerricStore.hset("user:42", %{"login_count" => "10"})
iex> FerricStore.hincrby("user:42", "login_count", 5)
{:ok, 15}

iex> FerricStore.hincrby("user:42", "new_counter", 1)
{:ok, 1}

hincrbyfloat(key, field, amount)

@spec hincrbyfloat(key(), binary(), float()) :: {:ok, binary()} | {:error, binary()}

Increments the float value of field in the hash at key by amount.

If the field does not exist, it is created with 0 before incrementing. Returns the new value as a string. Returns {:error, reason} if the field value is not a valid number.

Examples

iex> FerricStore.hset("product:99", %{"price" => "10.0"})
iex> FerricStore.hincrbyfloat("product:99", "price", 2.5)
{:ok, "12.5"}

iex> FerricStore.hincrbyfloat("product:99", "discount", 0.15)
{:ok, "0.15"}

hkeys(key)

@spec hkeys(key()) :: {:ok, [binary()]}

Returns all field names from the hash stored at key.

Returns {:ok, []} if the key does not exist. The order of returned field names is not guaranteed.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice", "age" => "30"})
iex> {:ok, fields} = FerricStore.hkeys("user:42")
iex> Enum.sort(fields)
["age", "name"]

iex> FerricStore.hkeys("no_such_hash")
{:ok, []}

hlen(key)

@spec hlen(key()) :: {:ok, non_neg_integer()}

Returns the number of fields in the hash stored at key.

Returns {:ok, 0} if the key does not exist.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice", "age" => "30", "email" => "a@b.c"})
iex> FerricStore.hlen("user:42")
{:ok, 3}

iex> FerricStore.hlen("no_such_hash")
{:ok, 0}

hmget(key, fields)

@spec hmget(key(), [binary()]) :: {:ok, [binary() | nil]}

Returns values for the specified fields from the hash at key.

Returns nil for fields that do not exist. The order of returned values matches the order of the requested fields.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice", "age" => "30"})
iex> FerricStore.hmget("user:42", ["name", "missing", "age"])
{:ok, ["alice", nil, "30"]}

iex> FerricStore.hmget("no_such_hash", ["a", "b"])
{:ok, [nil, nil]}

hrandfield(key, count \\ nil)

@spec hrandfield(key(), integer() | nil) :: {:ok, binary() | [binary()] | nil}

Returns one or more random field names from the hash at key.

Without count, returns a single field name or nil if the hash is empty. With positive count, returns up to count unique fields. With negative count, returns abs(count) fields with possible duplicates.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice", "age" => "30", "email" => "a@b.c"})
iex> {:ok, field} = FerricStore.hrandfield("user:42")
iex> field in ["name", "age", "email"]
true

iex> {:ok, fields} = FerricStore.hrandfield("user:42", 2)
iex> length(fields)
2

iex> FerricStore.hrandfield("nonexistent")
{:ok, nil}

hset(key, fields)

@spec hset(key(), %{required(binary()) => binary()}) :: :ok

Sets one or more fields in the hash stored at key.

fields is a map of %{field_name => value}. Field names and values are stored as binaries. If the hash does not exist, a new one is created. Existing fields are overwritten.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice", "age" => "30"})
:ok

iex> FerricStore.hset("user:42", %{"name" => "bob"})
:ok

hsetnx(key, field, value)

@spec hsetnx(key(), binary(), binary()) :: {:ok, boolean()}

Sets field in the hash at key only if the field does not already exist.

Returns {:ok, true} if the field was set, {:ok, false} if it already existed.

Examples

iex> FerricStore.hsetnx("user:42", "name", "alice")
{:ok, true}

iex> FerricStore.hsetnx("user:42", "name", "bob")
{:ok, false}

hstrlen(key, field)

@spec hstrlen(key(), binary()) :: {:ok, non_neg_integer()}

Returns the string length of the value for field in the hash at key.

Returns {:ok, 0} if the field or the key does not exist.

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice"})
iex> FerricStore.hstrlen("user:42", "name")
{:ok, 5}

iex> FerricStore.hstrlen("user:42", "missing")
{:ok, 0}

hvals(key)

@spec hvals(key()) :: {:ok, [binary()]}

Returns all field values from the hash stored at key.

Returns {:ok, []} if the key does not exist. The order of returned values corresponds to the order of fields (not guaranteed to be insertion order).

Examples

iex> FerricStore.hset("user:42", %{"name" => "alice", "age" => "30"})
iex> {:ok, vals} = FerricStore.hvals("user:42")
iex> Enum.sort(vals)
["30", "alice"]

iex> FerricStore.hvals("no_such_hash")
{:ok, []}

incr(key)

@spec incr(key()) :: {:ok, integer()} | {:error, binary()}

Increments the integer value stored at key by 1.

If the key does not exist, it is initialized to 0 before incrementing, resulting in a value of 1. Returns {:error, reason} if the stored value cannot be parsed as an integer.

Examples

iex> FerricStore.incr("page:views:home")
{:ok, 1}

iex> FerricStore.incr("page:views:home")
{:ok, 2}

iex> FerricStore.set("name", "alice")
:ok
iex> FerricStore.incr("name")
{:error, "ERR value is not an integer or out of range"}

incr_by(key, amount)

@spec incr_by(key(), integer()) :: {:ok, integer()} | {:error, binary()}

Increments the integer value stored at key by amount.

If the key does not exist, it is initialized to 0 before incrementing. Returns {:error, reason} if the stored value is not a valid integer.

Examples

iex> FerricStore.incr_by("page:views:home", 10)
{:ok, 10}

iex> FerricStore.incr_by("page:views:home", 5)
{:ok, 15}

iex> FerricStore.set("name", "alice")
:ok
iex> FerricStore.incr_by("name", 1)
{:error, "ERR value is not an integer or out of range"}

incr_by_float(key, amount)

@spec incr_by_float(key(), float()) :: {:ok, binary()} | {:error, binary()}

Increments the numeric value stored at key by a floating-point amount.

If the key does not exist, it is initialized to 0.0 before incrementing. The new value is returned as a string representation. Returns {:error, reason} if the stored value is not a valid number.

Examples

iex> FerricStore.incr_by_float("price:item:99", 3.14)
{:ok, "3.14"}

iex> FerricStore.set("balance:user:42", "100.50")
:ok
iex> FerricStore.incr_by_float("balance:user:42", -20.25)
{:ok, "80.25"}

json_arrappend(key, path, values)

@spec json_arrappend(key(), binary(), [binary()]) ::
  {:ok, term()} | {:error, binary()}

Appends one or more JSON values to the array at path in the document at key.

Parameters

  • key - the document key
  • path - JSONPath to an array
  • values - list of JSON-encoded strings to append

Returns

  • {:ok, new_array_length} on success.
  • {:error, reason} if the path is not an array.

Examples

iex> FerricStore.json_arrappend("user:42:prefs", "$.tags", [~s("vip"), ~s("beta")])
{:ok, [4]}

json_arrlen(key, path \\ "$")

@spec json_arrlen(key(), binary()) :: {:ok, integer()} | {:error, binary()}

Returns the length of the JSON array at path in the document at key.

Examples

iex> FerricStore.json_arrlen("user:42:prefs", "$.tags")
{:ok, [4]}

json_del(key, path \\ "$")

@spec json_del(key(), binary()) :: {:ok, term()} | {:error, binary()}

Deletes the value at path from the JSON document at key.

When path is "$", the entire document is deleted.

Returns

  • {:ok, deleted_count} - number of paths deleted.
  • {:error, reason} on failure.

Examples

iex> FerricStore.json_del("user:42:prefs", "$.theme")
{:ok, 1}

json_get(key, path \\ "$")

@spec json_get(key(), binary()) :: {:ok, binary()} | {:error, binary()}

Gets the JSON value at path from the document stored at key.

Parameters

  • key - the document key
  • path - JSONPath expression (default: "$" for the root)

Returns

  • {:ok, json_string} on success.
  • {:ok, nil} if the key does not exist.
  • {:error, reason} on failure.

Examples

iex> FerricStore.json_get("user:42:prefs", "$.theme")
{:ok, "["dark"]"}

iex> FerricStore.json_get("user:42:prefs")
{:ok, "[{"theme":"dark","lang":"en"}]"}

json_numincrby(key, path, increment)

@spec json_numincrby(key(), binary(), binary()) ::
  {:ok, binary()} | {:error, binary()}

Atomically increments a numeric value at path in the JSON document at key.

Parameters

  • key - the document key
  • path - JSONPath to a numeric value
  • increment - the increment amount as a string (e.g. "1", "0.5")

Returns

  • {:ok, new_value_string} on success.
  • {:error, reason} if the path is not a number.

Examples

iex> FerricStore.json_numincrby("config:app", "$.retry_count", "1")
{:ok, "[4]"}

json_objkeys(key, path \\ "$")

@spec json_objkeys(key(), binary()) :: {:ok, list()} | {:error, binary()}

Returns the keys of the JSON object at path in the document at key.

Examples

iex> FerricStore.json_objkeys("user:42:prefs")
{:ok, [["theme", "lang"]]}

json_objlen(key, path \\ "$")

@spec json_objlen(key(), binary()) :: {:ok, integer()} | {:error, binary()}

Returns the number of keys in the JSON object at path in the document at key.

Examples

iex> FerricStore.json_objlen("user:42:prefs")
{:ok, [2]}

json_set(key, path, value)

@spec json_set(key(), binary(), binary()) :: :ok | {:error, binary()}

Sets a JSON value at path in the document stored at key.

Creates the document if it does not exist (when path is "$"). Uses JSONPath syntax for nested access. Ideal for storing user preferences, feature flags, and nested configuration.

Parameters

  • key - the document key
  • path - JSONPath expression (e.g. "$", "$.settings.theme")
  • value - JSON-encoded string to store

Returns

  • :ok on success.
  • {:error, reason} on failure.

Examples

iex> FerricStore.json_set("user:42:prefs", "$", ~s({"theme":"dark","lang":"en"}))
:ok

iex> FerricStore.json_set("user:42:prefs", "$.theme", ~s("light"))
:ok

json_strlen(key, path \\ "$")

@spec json_strlen(key(), binary()) :: {:ok, integer()} | {:error, binary()}

Returns the length of the JSON string at path in the document at key.

Examples

iex> FerricStore.json_strlen("user:42:prefs", "$.theme")
{:ok, [4]}

json_type(key, path \\ "$")

@spec json_type(key(), binary()) :: {:ok, binary()} | {:error, binary()}

Returns the JSON type of the value at path in the document at key.

Returns

  • {:ok, type} where type is one of "object", "array", "string", "number", "boolean", "null".
  • {:error, reason} on failure.

Examples

iex> FerricStore.json_type("user:42:prefs", "$.theme")
{:ok, ["string"]}

keys(pattern \\ "*")

@spec keys(binary()) :: {:ok, [binary()]}

Returns all keys matching pattern (glob-style).

The pattern supports glob-style wildcards:

  • * - matches any sequence of characters
  • ? - matches any single character

Examples

iex> FerricStore.set("user:1:name", "alice")
:ok
iex> FerricStore.set("user:2:name", "bob")
:ok
iex> FerricStore.set("order:1", "pending")
:ok
iex> {:ok, user_keys} = FerricStore.keys("user:*")
iex> Enum.sort(user_keys)
["user:1:name", "user:2:name"]

iex> {:ok, all} = FerricStore.keys()
iex> length(all) >= 3
true

lindex(key, index)

@spec lindex(key(), integer()) :: {:ok, binary() | nil}

Returns the element at index in the list stored at key.

Negative indices count from the end (-1 is the last element). Returns {:ok, nil} for out-of-range indices or nonexistent keys.

Examples

iex> FerricStore.rpush("tasks:queue", ["task_a", "task_b", "task_c"])
iex> FerricStore.lindex("tasks:queue", 0)
{:ok, "task_a"}

iex> FerricStore.lindex("tasks:queue", -1)
{:ok, "task_c"}

iex> FerricStore.lindex("tasks:queue", 99)
{:ok, nil}

linsert(key, direction, pivot, element)

@spec linsert(key(), :before | :after, binary(), binary()) :: {:ok, integer()}

Inserts element before or after pivot in the list at key.

Returns {:ok, new_length} if the pivot was found, or {:ok, -1} if the pivot was not found. Returns {:ok, 0} if the key does not exist.

Examples

iex> FerricStore.rpush("tasks:queue", ["task_a", "task_b", "task_c"])
iex> FerricStore.linsert("tasks:queue", :before, "task_b", "task_new")
{:ok, 4}

iex> FerricStore.linsert("tasks:queue", :after, "task_c", "task_last")
{:ok, 5}

iex> FerricStore.linsert("tasks:queue", :before, "nonexistent", "x")
{:ok, -1}

llen(key)

@spec llen(key()) :: {:ok, non_neg_integer()}

Returns the length of the list stored at key.

Returns {:ok, 0} if the key does not exist.

Examples

iex> FerricStore.rpush("tasks:queue", ["task_a", "task_b", "task_c"])
iex> FerricStore.llen("tasks:queue")
{:ok, 3}

iex> FerricStore.llen("nonexistent")
{:ok, 0}

lmove(source, destination, from_dir, to_dir)

@spec lmove(key(), key(), :left | :right, :left | :right) :: {:ok, binary() | nil}

Atomically moves an element from one list to another.

Pops from from_dir of source and pushes to to_dir of destination. Returns {:ok, nil} if the source list is empty or does not exist.

Examples

iex> FerricStore.rpush("inbox", ["msg_a", "msg_b"])
iex> FerricStore.lmove("inbox", "processing", :left, :right)
{:ok, "msg_a"}

iex> FerricStore.lmove("empty_list", "dst", :left, :right)
{:ok, nil}

lock(key, owner, ttl_ms)

@spec lock(key(), binary(), pos_integer()) :: :ok | {:error, binary()}

Acquires a distributed mutex lock on key with the given owner identity and TTL.

Only one owner can hold a lock at a time. If the lock is already held by a different owner, returns an error. Use unlock/2 to release and extend/3 to renew the TTL before expiry.

Parameters

  • key - the lock key (e.g. "lock:order:123")
  • owner - unique owner identifier (e.g. a UUID or node name)
  • ttl_ms - lock duration in milliseconds (auto-expires as a safety net)

Returns

  • :ok if the lock was acquired.
  • {:error, reason} if the lock is held by another owner.

Examples

iex> FerricStore.lock("lock:order:123", "worker_abc", 30_000)
:ok

iex> FerricStore.lock("lock:order:123", "worker_xyz", 30_000)
{:error, "ERR lock is held by another owner"}

lpop(key, count \\ 1)

@spec lpop(key(), pos_integer()) :: {:ok, binary() | [binary()] | nil}

Pops one or more elements from the left (head) of the list stored at key.

When count is 1 (the default), returns a single element. When count is greater than 1, returns a list of elements. Returns {:ok, nil} if the key does not exist or the list is empty.

Examples

iex> FerricStore.rpush("tasks:queue", ["task_a", "task_b", "task_c"])
iex> FerricStore.lpop("tasks:queue")
{:ok, "task_a"}

iex> FerricStore.lpop("tasks:queue", 2)
{:ok, ["task_b", "task_c"]}

iex> FerricStore.lpop("empty_queue")
{:ok, nil}

lpos(key, element, opts \\ [])

@spec lpos(key(), binary(), keyword()) :: {:ok, integer() | [integer()] | nil}

Finds the position of element in the list at key.

Returns the zero-based index of the first match, or {:ok, nil} if not found. When :count is specified, returns a list of indices.

Options

  • :rank - Skip the first N-1 matches and return starting from the Nth (default: 1). Negative rank searches from tail.
  • :count - Return up to N positions. 0 means all. When given, always returns a list.
  • :maxlen - Limit scan to the first N elements (default: 0, no limit).

Examples

iex> FerricStore.rpush("tasks:queue", ["retry", "send", "retry", "process"])
iex> FerricStore.lpos("tasks:queue", "retry")
{:ok, 0}

iex> FerricStore.lpos("tasks:queue", "retry", count: 0)
{:ok, [0, 2]}

iex> FerricStore.lpos("tasks:queue", "missing")
{:ok, nil}

lpush(key, elements)

@spec lpush(key(), [binary()]) :: {:ok, non_neg_integer()}

Pushes one or more elements to the left (head) of the list stored at key.

If the key does not exist, a new list is created. Elements are inserted left-to-right, so the last element in the list ends up as the leftmost element (matching Redis LPUSH semantics).

Examples

iex> FerricStore.lpush("tasks:queue", ["send_email"])
{:ok, 1}

iex> FerricStore.lpush("tasks:queue", ["generate_report", "resize_image"])
{:ok, 3}

lrange(key, start, stop)

@spec lrange(key(), integer(), integer()) :: {:ok, [binary()]}

Returns elements from the list stored at key within the range start..stop.

Both start and stop are zero-based, inclusive indices. Negative indices count from the end of the list (-1 is the last element).

Examples

iex> FerricStore.rpush("tasks:queue", ["task_a", "task_b", "task_c"])
iex> FerricStore.lrange("tasks:queue", 0, -1)
{:ok, ["task_a", "task_b", "task_c"]}

iex> FerricStore.lrange("tasks:queue", 1, 1)
{:ok, ["task_b"]}

iex> FerricStore.lrange("nonexistent", 0, -1)
{:ok, []}

lrem(key, count, element)

@spec lrem(key(), integer(), binary()) :: {:ok, non_neg_integer()}

Removes occurrences of element from the list at key.

The count argument controls the direction and number of removals:

  • count > 0 - Remove up to count occurrences scanning from head to tail.
  • count < 0 - Remove up to abs(count) occurrences scanning from tail to head.
  • count == 0 - Remove all occurrences.

Examples

iex> FerricStore.rpush("tasks:queue", ["retry", "send", "retry", "retry"])
iex> FerricStore.lrem("tasks:queue", 0, "retry")
{:ok, 3}

iex> FerricStore.rpush("tasks:queue", ["a", "b", "a"])
iex> FerricStore.lrem("tasks:queue", 1, "a")
{:ok, 1}

lset(key, index, element)

@spec lset(key(), integer(), binary()) :: :ok | {:error, binary()}

Sets the element at index in the list stored at key.

Returns :ok on success, or {:error, reason} if the index is out of range or the key does not exist.

Examples

iex> FerricStore.rpush("tasks:queue", ["task_a", "task_b", "task_c"])
iex> FerricStore.lset("tasks:queue", 1, "task_b_updated")
:ok

iex> FerricStore.lset("tasks:queue", 99, "value")
{:error, "ERR index out of range"}

mget(keys)

@spec mget([key()]) :: {:ok, [value() | nil]}

Gets values for multiple keys in a single call.

Returns {:ok, values} where values is a list in the same order as the input keys. Missing or expired keys appear as nil in the result list.

Examples

iex> FerricStore.set("user:1:name", "alice")
:ok
iex> FerricStore.set("user:2:name", "bob")
:ok
iex> FerricStore.mget(["user:1:name", "user:2:name", "user:3:name"])
{:ok, ["alice", "bob", nil]}

mset(pairs)

@spec mset(%{required(key()) => value()}) :: :ok

Sets multiple key-value pairs in a single call.

All pairs are written without expiry. Use set/3 with :ttl if individual keys need time-to-live.

Examples

iex> FerricStore.mset(%{"user:1:name" => "alice", "user:2:name" => "bob"})
:ok

iex> FerricStore.get("user:1:name")
{:ok, "alice"}

msetnx(pairs)

@spec msetnx(%{required(key()) => value()}) :: {:ok, boolean()}

Sets multiple key-value pairs only if none of the given keys already exist.

This is atomic: either all keys are set, or none are. If any key in the map already exists, the entire operation is skipped and {:ok, false} is returned.

Examples

iex> FerricStore.msetnx(%{"user:1:email" => "a@test.com", "user:2:email" => "b@test.com"})
{:ok, true}

iex> FerricStore.msetnx(%{"user:1:email" => "new@test.com", "user:3:email" => "c@test.com"})
{:ok, false}

multi(fun)

@spec multi((FerricStore.Tx.t() -> FerricStore.Tx.t())) ::
  {:ok, [term()]} | {:error, binary()}

Executes a sequence of commands atomically as a transaction.

The provided function receives a FerricStore.Tx accumulator and should pipe commands into it. All commands execute in order and results are returned.

Examples

{:ok, [:ok, {:ok, "v1"}]} = FerricStore.multi(fn tx ->
  tx
  |> FerricStore.Tx.set("k1", "v1")
  |> FerricStore.Tx.get("k1")
end)

packed_batch_get(packed_keys)

@spec packed_batch_get(binary()) :: binary()

Packed binary batch GET — minimal distribution overhead.

Input: single binary with packed keys: <<count::32, key_len::16, key::binary, ...>> Output: single binary with packed values: <<val_len::32, val::binary, ...>> where val_len=0xFFFFFFFF means nil.

One flat binary over distribution instead of a list of N binaries — eliminates per-element external term format encoding.

persist(key)

@spec persist(key()) :: {:ok, boolean()}

Removes the TTL from key, making it persist indefinitely.

Returns {:ok, true} if an expiry was removed, or {:ok, false} if the key does not exist or already has no TTL.

Examples

iex> FerricStore.set("session:abc", "data", ttl: 60_000)
:ok
iex> FerricStore.persist("session:abc")
{:ok, true}
iex> FerricStore.ttl("session:abc")
{:ok, nil}

iex> FerricStore.persist("permanent:key")
{:ok, false}

iex> FerricStore.persist("nonexistent:key")
{:ok, false}

pexpire(key, ttl_ms)

@spec pexpire(key(), non_neg_integer()) :: {:ok, boolean()}

Sets a TTL in milliseconds on an existing key.

This is an alias for expire/2 -- both accept milliseconds.

Examples

iex> FerricStore.set("rate_limit:user:42", "3")
:ok
iex> FerricStore.pexpire("rate_limit:user:42", 30_000)
{:ok, true}

iex> FerricStore.pexpire("nonexistent:key", 5_000)
{:ok, false}

pexpireat(key, unix_ts_ms)

@spec pexpireat(key(), non_neg_integer()) :: {:ok, boolean()}

Sets the key to expire at the given absolute Unix timestamp (in milliseconds).

Like expireat/2 but with millisecond precision. Returns {:ok, false} if the key does not exist.

Examples

iex> FerricStore.set("event:flash_sale", "active")
:ok
iex> FerricStore.pexpireat("event:flash_sale", 1_700_000_000_000)
{:ok, true}

iex> FerricStore.pexpireat("nonexistent:key", 1_700_000_000_000)
{:ok, false}

pexpiretime(key)

@spec pexpiretime(key()) :: {:ok, integer()}

Returns the absolute Unix timestamp (in milliseconds) at which key will expire.

Like expiretime/1 but with millisecond precision. Returns {:ok, -1} if the key has no expiry, and {:ok, -2} if it does not exist.

Examples

iex> FerricStore.set("session:abc", "data", ttl: 60_000)
:ok
iex> {:ok, ts_ms} = FerricStore.pexpiretime("session:abc")
iex> ts_ms > 0
true

iex> FerricStore.set("permanent:key", "data")
:ok
iex> FerricStore.pexpiretime("permanent:key")
{:ok, -1}

iex> FerricStore.pexpiretime("nonexistent:key")
{:ok, -2}

pfadd(key, elements)

@spec pfadd(key(), [binary()]) :: {:ok, boolean()} | {:error, binary()}

Adds elements to the HyperLogLog at key for approximate cardinality counting.

A HyperLogLog uses ~12KB of memory to estimate the number of unique elements in a set with a standard error of 0.81%. Ideal for counting unique visitors, distinct IPs, or unique events without storing every value.

Returns

  • {:ok, true} if the internal registers were modified (new unique element likely).
  • {:ok, false} if the registers were not modified.
  • {:error, reason} on failure.

Examples

iex> FerricStore.pfadd("visitors:2024-03-28", ["user_1", "user_2", "user_3"])
{:ok, true}

pfcount(keys)

@spec pfcount([key()]) :: {:ok, non_neg_integer()} | {:error, binary()}

Returns the approximate number of unique elements across one or more HyperLogLogs.

When given multiple keys, computes the cardinality of their union without modifying the underlying structures.

Returns

  • {:ok, count} - estimated unique element count.
  • {:error, reason} on failure.

Examples

iex> FerricStore.pfcount(["visitors:2024-03-28"])
{:ok, 3}

iex> FerricStore.pfcount(["visitors:2024-03-27", "visitors:2024-03-28"])
{:ok, 5}

pfmerge(dest_key, source_keys)

@spec pfmerge(key(), [key()]) :: :ok | {:error, binary()}

Merges multiple HyperLogLog keys into dest_key.

The resulting HyperLogLog approximates the cardinality of the union of all source sets. Useful for computing weekly/monthly unique counts from daily ones.

Returns

  • :ok on success.
  • {:error, reason} on failure.

Examples

iex> FerricStore.pfmerge("visitors:2024-w13", ["visitors:2024-03-25", "visitors:2024-03-26", "visitors:2024-03-27"])
:ok

ping()

@spec ping() :: {:ok, binary()}

Health check that returns {:ok, "PONG"}.

Examples

iex> FerricStore.ping()
{:ok, "PONG"}

pipeline(fun)

@spec pipeline((FerricStore.Pipe.t() -> FerricStore.Pipe.t())) :: {:ok, [term()]}

Batches multiple commands into a single group-commit entry.

The provided function receives a FerricStore.Pipe accumulator and should pipe commands into it. All commands are executed atomically on completion.

Examples

results = FerricStore.pipeline(fn pipe ->
  pipe
  |> FerricStore.Pipe.set("key1", "val1")
  |> FerricStore.Pipe.set("key2", "val2")
  |> FerricStore.Pipe.incr("counter")
end)

Returns

  • {:ok, results} - a list of results for each piped command, in order.

psetex(key, milliseconds, value)

@spec psetex(key(), pos_integer(), value()) :: :ok

Sets key to value with a TTL in milliseconds.

This is a convenience wrapper equivalent to set(key, value, ttl: milliseconds).

Examples

iex> FerricStore.psetex("rate_limit:user:42", 500, "1")
:ok

iex> FerricStore.psetex("debounce:click", 200, "pending")
:ok

pttl(key)

@spec pttl(key()) :: {:ok, non_neg_integer() | nil}

Returns the remaining time-to-live in milliseconds for key.

This is an alias for ttl/1 -- both return millisecond precision. Returns {:ok, nil} if the key has no expiry or does not exist.

Examples

iex> FerricStore.set("cache:result", "data", ttl: 30_000)
:ok
iex> {:ok, ms} = FerricStore.pttl("cache:result")
iex> ms > 0 and ms <= 30_000
true

iex> FerricStore.pttl("nonexistent:key")
{:ok, nil}

randomkey()

@spec randomkey() :: {:ok, key() | nil}

Returns a random key from the store, or {:ok, nil} if the store is empty.

Returns a random key from the store.

Examples

iex> FerricStore.set("key:a", "1")
:ok
iex> {:ok, key} = FerricStore.randomkey()
iex> is_binary(key)
true

iex> # When the store is empty:
iex> FerricStore.randomkey()
{:ok, nil}

ratelimit_add(key, window_ms, max, count \\ 1)

@spec ratelimit_add(key(), pos_integer(), pos_integer(), pos_integer()) ::
  {:ok, list()}

Records count events against the sliding-window rate limiter at key.

Uses a sliding window algorithm to track request counts within a time window. Returns the current count and whether the limit has been exceeded. Ideal for API rate limiting, abuse prevention, and throttling.

Parameters

  • key - the rate limit key (e.g. "ratelimit:api:user:42")
  • window_ms - sliding window duration in milliseconds
  • max - maximum allowed events within the window
  • count - number of events to record (default: 1)

Returns

  • {:ok, [allowed, current_count]} where allowed is 1 (allowed) or 0 (rate limit exceeded), and current_count is the total events in the window.

Examples

iex> FerricStore.ratelimit_add("ratelimit:api:user:42", 60_000, 100)
{:ok, [1, 1]}

iex> FerricStore.ratelimit_add("ratelimit:api:user:42", 60_000, 100, 5)
{:ok, [1, 6]}

ready?()

@spec ready?() :: boolean()

Returns true if FerricStore is ready to serve requests.

Examples

iex> FerricStore.ready?()
true

rename(source, destination)

@spec rename(key(), key()) :: :ok | {:error, binary()}

Renames source to destination, overwriting destination if it exists.

The value and TTL are transferred to the new key name, and the source key is deleted. Returns {:error, reason} if the source does not exist.

Examples

iex> FerricStore.set("temp:upload:abc", "file_data")
:ok
iex> FerricStore.rename("temp:upload:abc", "file:abc")
:ok
iex> FerricStore.get("file:abc")
{:ok, "file_data"}
iex> FerricStore.exists("temp:upload:abc")
false

iex> FerricStore.rename("nonexistent", "dst")
{:error, "ERR no such key"}

renamenx(source, destination)

@spec renamenx(key(), key()) :: {:ok, boolean()} | {:error, binary()}

Renames source to destination only if destination does not already exist.

Unlike rename/2, this will not overwrite an existing destination key. The value and TTL are transferred on success.

Examples

iex> FerricStore.set("temp:import:1", "data")
:ok
iex> FerricStore.renamenx("temp:import:1", "import:1")
{:ok, true}

iex> FerricStore.set("import:2", "existing")
:ok
iex> FerricStore.set("temp:import:2", "new_data")
:ok
iex> FerricStore.renamenx("temp:import:2", "import:2")
{:ok, false}

iex> FerricStore.renamenx("nonexistent", "dst")
{:error, "ERR no such key"}

rpop(key, count \\ 1)

@spec rpop(key(), pos_integer()) :: {:ok, binary() | [binary()] | nil}

Pops one or more elements from the right (tail) of the list stored at key.

When count is 1 (the default), returns a single element. When count is greater than 1, returns a list of elements. Returns {:ok, nil} if the key does not exist or the list is empty.

Examples

iex> FerricStore.rpush("tasks:queue", ["task_a", "task_b", "task_c"])
iex> FerricStore.rpop("tasks:queue")
{:ok, "task_c"}

iex> FerricStore.rpop("tasks:queue", 2)
{:ok, ["task_b", "task_a"]}

iex> FerricStore.rpop("empty_queue")
{:ok, nil}

rpush(key, elements)

@spec rpush(key(), [binary()]) :: {:ok, non_neg_integer()}

Pushes one or more elements to the right (tail) of the list stored at key.

If the key does not exist, a new list is created.

Examples

iex> FerricStore.rpush("tasks:queue", ["send_email"])
{:ok, 1}

iex> FerricStore.rpush("tasks:queue", ["generate_report", "resize_image"])
{:ok, 3}

sadd(key, members)

@spec sadd(key(), [binary()]) :: {:ok, non_neg_integer()}

Adds one or more members to the set stored at key.

If the key does not exist, a new set is created. Members that already exist in the set are ignored. Returns the count of members actually added.

Examples

iex> FerricStore.sadd("article:42:tags", ["elixir", "rust", "database"])
{:ok, 3}

iex> FerricStore.sadd("article:42:tags", ["rust", "performance"])
{:ok, 1}

scard(key)

@spec scard(key()) :: {:ok, non_neg_integer()}

Returns the number of members in the set stored at key (the set cardinality).

Returns {:ok, 0} if the key does not exist.

Examples

iex> FerricStore.sadd("article:42:tags", ["elixir", "rust", "database"])
iex> FerricStore.scard("article:42:tags")
{:ok, 3}

iex> FerricStore.scard("nonexistent")
{:ok, 0}

sdiff(keys)

@spec sdiff([key()]) :: {:ok, [binary()]}

Returns the set difference: members in the first set that are not in any of the other sets.

Handles cross-shard keys transparently. Returns {:ok, []} if the first key does not exist.

Examples

iex> FerricStore.sadd("frontend:tags", ["elixir", "react", "tailwind"])
iex> FerricStore.sadd("backend:tags", ["elixir", "postgres"])
iex> {:ok, diff} = FerricStore.sdiff(["frontend:tags", "backend:tags"])
iex> Enum.sort(diff)
["react", "tailwind"]

sdiffstore(destination, keys)

@spec sdiffstore(key(), [key()]) :: {:ok, non_neg_integer()}

Computes the set difference of the given keys and stores the result in destination.

Any existing value at destination is overwritten. Returns the number of elements in the resulting set.

Examples

iex> FerricStore.sadd("frontend:tags", ["elixir", "react", "tailwind"])
iex> FerricStore.sadd("backend:tags", ["elixir", "postgres"])
iex> FerricStore.sdiffstore("frontend_only:tags", ["frontend:tags", "backend:tags"])
{:ok, 2}

set(key, value, opts \\ [])

@spec set(key(), value(), set_opts()) ::
  :ok | {:ok, value() | nil} | nil | {:error, binary()}

Sets key to value with optional TTL and condition flags.

Options

  • :ttl - Time-to-live in milliseconds (relative). When omitted or 0, the key never expires. Mutually exclusive with :exat, :pxat, :keepttl.
  • :exat - Absolute Unix timestamp in seconds at which the key expires. Mutually exclusive with :ttl, :pxat, :keepttl.
  • :pxat - Absolute Unix timestamp in milliseconds at which the key expires. Mutually exclusive with :ttl, :exat, :keepttl.
  • :nx - Only set the key if it does not already exist.
  • :xx - Only set the key if it already exists.
  • :get - Return the old value stored at the key before overwriting. When set, the return value changes to {:ok, old_value} (or {:ok, nil} if the key did not exist).
  • :keepttl - Retain the existing TTL associated with the key instead of clearing it. Mutually exclusive with :ttl, :exat, :pxat.

Examples

iex> FerricStore.set("user:42:name", "alice")
:ok

iex> FerricStore.set("session:abc", "token_data", ttl: :timer.hours(1))
:ok

iex> FerricStore.set("cache:page:home", "html", exat: 1711234567)
:ok

iex> FerricStore.set("lock:order:99", "owner_1", nx: true)
:ok

iex> FerricStore.set("lock:order:99", "owner_2", nx: true)
nil

iex> FerricStore.set("counter", "0")
:ok
iex> FerricStore.set("counter", "100", get: true)
{:ok, "0"}

iex> FerricStore.set("missing", "val", get: true)
{:ok, nil}

iex> FerricStore.set("session:abc", "refreshed", keepttl: true)
:ok

Returns {:error, reason} if the value exceeds the configured max_value_size.

setbit(key, offset, bit_value)

@spec setbit(key(), non_neg_integer(), 0 | 1) :: {:ok, 0 | 1} | {:error, binary()}

Sets or clears the bit at offset in the string value stored at key.

Returns the original bit value at that position.

Examples

{:ok, 0} = FerricStore.setbit("key", 7, 1)

setex(key, seconds, value)

@spec setex(key(), pos_integer(), value()) :: :ok

Sets key to value with a TTL in seconds.

This is a convenience wrapper equivalent to set(key, value, ttl: seconds * 1_000).

Examples

iex> FerricStore.setex("session:abc", 3600, "token_data")
:ok

iex> FerricStore.setex("cache:query:recent", 60, "["row1","row2"]")
:ok

setnx(key, value)

@spec setnx(key(), value()) :: {:ok, boolean()}

Sets key to value only if the key does not already exist.

Returns {:ok, true} if the key was created, or {:ok, false} if the key already existed and the write was skipped.

Examples

iex> FerricStore.setnx("lock:job:import", "worker_1")
{:ok, true}

iex> FerricStore.setnx("lock:job:import", "worker_2")
{:ok, false}

setrange(key, offset, value)

@spec setrange(key(), non_neg_integer(), binary()) :: {:ok, non_neg_integer()}

Overwrites part of the string stored at key starting at byte offset.

If the key does not exist, or the existing string is shorter than offset, the value is zero-padded to reach the offset before writing. Returns {:ok, new_byte_length} with the total length after the write.

Examples

iex> FerricStore.set("greeting", "Hello World")
:ok
iex> FerricStore.setrange("greeting", 6, "Redis")
{:ok, 11}
iex> FerricStore.get("greeting")
{:ok, "Hello Redis"}

iex> FerricStore.setrange("padded:key", 5, "!")
{:ok, 6}

shutdown()

@spec shutdown() :: :ok

Gracefully shuts down FerricStore, flushing all pending data to disk.

Flushes Raft batchers, BitcaskWriters, shard pending writes, and triggers a WAL rollover. Call before stopping the application to ensure zero data loss.

Examples

FerricStore.shutdown()
Application.stop(:ferricstore)

sinter(keys)

@spec sinter([key()]) :: {:ok, [binary()]}

Returns the set intersection: members common to all given sets.

Handles cross-shard keys transparently. Returns {:ok, []} if any key does not exist.

Examples

iex> FerricStore.sadd("frontend:tags", ["elixir", "react", "tailwind"])
iex> FerricStore.sadd("backend:tags", ["elixir", "postgres"])
iex> FerricStore.sinter(["frontend:tags", "backend:tags"])
{:ok, ["elixir"]}

sintercard(keys, opts \\ [])

@spec sintercard(
  [key()],
  keyword()
) :: {:ok, non_neg_integer()}

Returns the cardinality of the intersection of all given sets.

More efficient than sinter/1 when you only need the count, not the actual members.

Options

  • :limit - Stop counting after reaching this limit (0 means no limit, default: 0). Useful for early termination on large sets.

Examples

iex> FerricStore.sadd("frontend:tags", ["elixir", "react", "tailwind"])
iex> FerricStore.sadd("backend:tags", ["elixir", "postgres", "tailwind"])
iex> FerricStore.sintercard(["frontend:tags", "backend:tags"])
{:ok, 2}

iex> FerricStore.sintercard(["frontend:tags", "backend:tags"], limit: 1)
{:ok, 1}

sinterstore(destination, keys)

@spec sinterstore(key(), [key()]) :: {:ok, non_neg_integer()}

Computes the set intersection of the given keys and stores the result in destination.

Any existing value at destination is overwritten. Returns the number of elements in the resulting set.

Examples

iex> FerricStore.sadd("frontend:tags", ["elixir", "react"])
iex> FerricStore.sadd("backend:tags", ["elixir", "postgres"])
iex> FerricStore.sinterstore("shared:tags", ["frontend:tags", "backend:tags"])
{:ok, 1}

sismember(key, member)

@spec sismember(key(), binary()) :: {:ok, boolean()} | {:error, binary()}

Checks whether member is a member of the set stored at key.

Returns {:ok, true} if the member exists, {:ok, false} otherwise. Returns {:ok, false} if the key does not exist.

Examples

iex> FerricStore.sadd("article:42:tags", ["elixir", "rust"])
iex> FerricStore.sismember("article:42:tags", "elixir")
{:ok, true}

iex> FerricStore.sismember("article:42:tags", "python")
{:ok, false}

iex> FerricStore.sismember("nonexistent", "member")
{:ok, false}

smembers(key)

@spec smembers(key()) :: {:ok, [binary()]}

Returns all members of the set stored at key.

Returns {:ok, []} if the key does not exist. The order of returned members is not guaranteed.

Examples

iex> FerricStore.sadd("article:42:tags", ["elixir", "rust"])
iex> {:ok, members} = FerricStore.smembers("article:42:tags")
iex> Enum.sort(members)
["elixir", "rust"]

iex> FerricStore.smembers("nonexistent")
{:ok, []}

smismember(key, members)

@spec smismember(key(), [binary()]) :: {:ok, [0 | 1]}

Returns the membership status of multiple members in the set at key.

Returns a list of 1s and 0s corresponding to each member, in the same order as the input list.

Examples

iex> FerricStore.sadd("article:42:tags", ["elixir", "rust", "database"])
iex> FerricStore.smismember("article:42:tags", ["elixir", "python", "database"])
{:ok, [1, 0, 1]}

iex> FerricStore.smismember("nonexistent", ["a", "b"])
{:ok, [0, 0]}

spop(key, count \\ nil)

@spec spop(key(), non_neg_integer() | nil) :: {:ok, binary() | [binary()] | nil}

Removes and returns one or more random members from the set at key.

Without count, returns a single member or nil for empty/nonexistent sets. With count, returns a list of up to count removed members.

Examples

iex> FerricStore.sadd("article:42:tags", ["elixir", "rust", "database"])
iex> {:ok, tag} = FerricStore.spop("article:42:tags")
iex> tag in ["elixir", "rust", "database"]
true

iex> {:ok, tags} = FerricStore.spop("article:42:tags", 2)
iex> length(tags)
2

iex> FerricStore.spop("nonexistent")
{:ok, nil}

srandmember(key, count \\ nil)

@spec srandmember(key(), integer() | nil) :: {:ok, binary() | [binary()] | nil}

Returns one or more random members from the set at key without removing them.

Without count, returns a single member or nil for empty/nonexistent sets. With positive count, returns up to count unique members. With negative count, returns abs(count) members with possible duplicates.

Examples

iex> FerricStore.sadd("article:42:tags", ["elixir", "rust", "database"])
iex> {:ok, member} = FerricStore.srandmember("article:42:tags")
iex> member in ["elixir", "rust", "database"]
true

iex> {:ok, members} = FerricStore.srandmember("article:42:tags", 2)
iex> length(members)
2

iex> FerricStore.srandmember("nonexistent")
{:ok, nil}

srem(key, members)

@spec srem(key(), [binary()]) :: {:ok, non_neg_integer()}

Removes one or more members from the set stored at key.

Members that do not exist in the set are ignored. Returns the count of members actually removed.

Examples

iex> FerricStore.sadd("article:42:tags", ["elixir", "rust", "database"])
iex> FerricStore.srem("article:42:tags", ["rust"])
{:ok, 1}

iex> FerricStore.srem("article:42:tags", ["nonexistent"])
{:ok, 0}

strlen(key)

@spec strlen(key()) :: {:ok, non_neg_integer()}

Returns the byte length of the string value stored at key.

Returns {:ok, 0} if the key does not exist.

Examples

iex> FerricStore.set("user:42:name", "alice")
:ok
iex> FerricStore.strlen("user:42:name")
{:ok, 5}

iex> FerricStore.strlen("nonexistent:key")
{:ok, 0}

sunion(keys)

@spec sunion([key()]) :: {:ok, [binary()]}

Returns the set union: all unique members across all given sets.

Handles cross-shard keys transparently.

Examples

iex> FerricStore.sadd("frontend:tags", ["elixir", "react"])
iex> FerricStore.sadd("backend:tags", ["elixir", "postgres"])
iex> {:ok, union} = FerricStore.sunion(["frontend:tags", "backend:tags"])
iex> Enum.sort(union)
["elixir", "postgres", "react"]

sunionstore(destination, keys)

@spec sunionstore(key(), [key()]) :: {:ok, non_neg_integer()}

Computes the set union of the given keys and stores the result in destination.

Any existing value at destination is overwritten. Returns the number of elements in the resulting set.

Examples

iex> FerricStore.sadd("frontend:tags", ["elixir", "react"])
iex> FerricStore.sadd("backend:tags", ["elixir", "postgres"])
iex> FerricStore.sunionstore("all:tags", ["frontend:tags", "backend:tags"])
{:ok, 3}

tdigest_add(key, values)

@spec tdigest_add(key(), [number()]) :: :ok | {:error, binary()}

Adds one or more numeric observations to the T-Digest at key.

Parameters

  • key - the T-Digest key
  • values - list of numeric values to add

Examples

iex> FerricStore.tdigest_add("api:latency:ms", [12.5, 45.0, 3.2, 89.1, 150.0])
:ok

tdigest_byrank(key, ranks)

@spec tdigest_byrank(key(), [integer()]) :: {:ok, list()} | {:error, binary()}

Estimates the value at each given rank (0-based position in sorted order).

Returns

  • {:ok, [value, ...]} - estimated value at each rank.

Examples

iex> FerricStore.tdigest_byrank("api:latency:ms", [0, 2, 4])
{:ok, ["3.2", "45.0", "150.0"]}

tdigest_byrevrank(key, ranks)

@spec tdigest_byrevrank(key(), [integer()]) :: {:ok, list()} | {:error, binary()}

Estimates the value at each given reverse rank (0 = largest, 1 = second largest, etc.).

Returns

  • {:ok, [value, ...]} - estimated value at each reverse rank.

Examples

iex> FerricStore.tdigest_byrevrank("api:latency:ms", [0, 1])
{:ok, ["150.0", "89.1"]}

tdigest_cdf(key, values)

@spec tdigest_cdf(key(), [number()]) :: {:ok, list()} | {:error, binary()}

Estimates the cumulative distribution function (CDF) at the given values.

Returns the fraction of observations less than or equal to each value. For example, a CDF of 0.95 at value 100 means 95% of observations were <= 100.

Returns

  • {:ok, [fraction, ...]} - CDF value (0.0 to 1.0) at each input.

Examples

iex> FerricStore.tdigest_cdf("api:latency:ms", [50.0, 100.0])
{:ok, ["0.6", "0.8"]}

tdigest_create(key)

@spec tdigest_create(key()) :: :ok | {:error, binary()}

Creates a T-Digest structure at key for estimating quantiles and percentiles.

A T-Digest compactly summarizes a distribution of numeric values, enabling accurate estimation of percentiles (p50, p95, p99) with bounded memory. Ideal for latency monitoring, response time analysis, and SLA tracking.

Examples

iex> FerricStore.tdigest_create("api:latency:ms")
:ok

tdigest_info(key)

@spec tdigest_info(key()) :: {:ok, list()} | {:error, binary()}

Returns metadata about the T-Digest at key (compression, total observations, etc.).

Returns

  • {:ok, info_list} - flat key-value list of digest properties.
  • {:error, reason} if the digest does not exist.

Examples

iex> FerricStore.tdigest_info("api:latency:ms")
{:ok, ["Compression", 100, "Capacity", 610, "Merged nodes", 5, "Unmerged nodes", 0, "Merged weight", "5.0", "Unmerged weight", "0.0", "Total compressions", 1]}

tdigest_max(key)

@spec tdigest_max(key()) :: {:ok, binary()} | {:error, binary()}

Returns the maximum value observed in the T-Digest at key.

Examples

iex> FerricStore.tdigest_max("api:latency:ms")
{:ok, "150.0"}

tdigest_min(key)

@spec tdigest_min(key()) :: {:ok, binary()} | {:error, binary()}

Returns the minimum value observed in the T-Digest at key.

Examples

iex> FerricStore.tdigest_min("api:latency:ms")
{:ok, "3.2"}

tdigest_quantile(key, quantiles)

@spec tdigest_quantile(key(), [float()]) :: {:ok, list()} | {:error, binary()}

Estimates the values at the given quantile points (0.0 to 1.0).

For example, quantile 0.5 is the median, 0.95 is the 95th percentile.

Returns

  • {:ok, [value, ...]} - estimated value at each quantile.
  • {:error, reason} if the digest does not exist.

Examples

iex> FerricStore.tdigest_quantile("api:latency:ms", [0.5, 0.95, 0.99])
{:ok, ["45.0", "150.0", "150.0"]}

tdigest_rank(key, values)

@spec tdigest_rank(key(), [number()]) :: {:ok, list()} | {:error, binary()}

Estimates the rank (number of observations less than or equal to) for each value.

Returns

  • {:ok, [rank, ...]} - estimated rank per value.

Examples

iex> FerricStore.tdigest_rank("api:latency:ms", [50.0, 100.0])
{:ok, [3, 4]}

tdigest_reset(key)

@spec tdigest_reset(key()) :: :ok | {:error, binary()}

Resets the T-Digest at key, discarding all observations.

Examples

iex> FerricStore.tdigest_reset("api:latency:ms")
:ok

tdigest_revrank(key, values)

@spec tdigest_revrank(key(), [number()]) :: {:ok, list()} | {:error, binary()}

Estimates the reverse rank (number of observations greater than) for each value.

Returns

  • {:ok, [reverse_rank, ...]} - estimated reverse rank per value.

Examples

iex> FerricStore.tdigest_revrank("api:latency:ms", [50.0, 100.0])
{:ok, [2, 1]}

tdigest_trimmed_mean(key, lo, hi)

@spec tdigest_trimmed_mean(key(), float(), float()) ::
  {:ok, binary()} | {:error, binary()}

Computes the trimmed mean of values between quantile bounds lo and hi.

A trimmed mean excludes outliers by only averaging values within the specified quantile range. For example, tdigest_trimmed_mean(key, 0.1, 0.9) averages the middle 80% of the distribution.

Parameters

  • key - the T-Digest key
  • lo - lower quantile bound (0.0 to 1.0)
  • hi - upper quantile bound (0.0 to 1.0)

Examples

iex> FerricStore.tdigest_trimmed_mean("api:latency:ms", 0.1, 0.9)
{:ok, "45.5"}

topk_add(key, elements)

@spec topk_add(key(), [binary()]) :: {:ok, list()} | {:error, binary()}

Adds one or more elements to the Top-K tracker, updating frequency counts.

If an element displaces another from the top-k, the displaced element is returned.

Returns

  • {:ok, [displaced | nil, ...]} - nil if no element was displaced, or the name of the displaced element, one per input.

Examples

iex> FerricStore.topk_add("trending:searches", ["elixir", "rust", "golang"])
{:ok, [nil, nil, nil]}

topk_info(key)

@spec topk_info(key()) :: {:ok, list()} | {:error, binary()}

Returns metadata about the Top-K tracker at key (k, width, depth, decay).

Returns

  • {:ok, info_list} - flat key-value list of tracker properties.
  • {:error, reason} if the tracker does not exist.

Examples

iex> FerricStore.topk_info("trending:searches")
{:ok, ["k", 10, "width", 8, "depth", 7, "decay", "0.9"]}

topk_list(key)

@spec topk_list(key()) :: {:ok, [binary()]} | {:error, binary()}

Returns the current Top-K elements, ordered by estimated frequency (descending).

Returns

  • {:ok, [element, ...]} - the top-k element names.
  • {:error, reason} if the tracker does not exist.

Examples

iex> FerricStore.topk_list("trending:searches")
{:ok, ["elixir", "rust", "golang"]}

topk_query(key, elements)

@spec topk_query(key(), [binary()]) :: {:ok, list()} | {:error, binary()}

Checks whether elements are currently in the Top-K set.

Returns

  • {:ok, [0 | 1, ...]} - 1 if the element is in the top-k, 0 otherwise.

Examples

iex> FerricStore.topk_query("trending:searches", ["elixir", "obscure-lang"])
{:ok, [1, 0]}

topk_reserve(key, k)

@spec topk_reserve(key(), pos_integer()) :: :ok | {:error, binary()}

Creates a Top-K tracker that maintains the k most frequent elements.

A Top-K structure uses the Heavy Keeper algorithm to efficiently track the most popular items in a data stream with bounded memory. Ideal for trending topics, popular search queries, and hot product tracking.

Parameters

  • key - the Top-K tracker key
  • k - number of top elements to track

Examples

iex> FerricStore.topk_reserve("trending:searches", 10)
:ok

ttl(key)

@spec ttl(key()) :: {:ok, non_neg_integer() | nil}

Returns the remaining time-to-live in milliseconds for key.

Returns {:ok, ms} if the key has a TTL set, or {:ok, nil} if the key has no expiry or does not exist.

Examples

iex> FerricStore.set("session:abc", "data", ttl: 60_000)
:ok
iex> {:ok, ms} = FerricStore.ttl("session:abc")
iex> ms > 0 and ms <= 60_000
true

iex> FerricStore.set("permanent:key", "data")
:ok
iex> FerricStore.ttl("permanent:key")
{:ok, nil}

iex> FerricStore.ttl("nonexistent:key")
{:ok, nil}

type(key)

@spec type(key()) :: {:ok, binary()}

Returns the data type of the value stored at key.

The returned type string reflects the underlying data structure: "string", "hash", "list", "set", "zset", "stream", or "none" if the key does not exist.

Examples

iex> FerricStore.set("user:42:name", "alice")
:ok
iex> FerricStore.type("user:42:name")
{:ok, "string"}

iex> FerricStore.hset("user:42", %{"name" => "alice"})
:ok
iex> FerricStore.type("user:42")
{:ok, "hash"}

iex> FerricStore.type("nonexistent:key")
{:ok, "none"}

unlock(key, owner)

@spec unlock(key(), binary()) :: {:ok, 1} | {:error, binary()}

Releases the lock on key, but only if it is currently held by owner.

This ensures that a lock holder cannot accidentally release someone else's lock (e.g. after a timeout and re-acquisition by another process).

Returns

  • {:ok, 1} if the lock was released.
  • {:error, reason} if the lock is not held by owner.

Examples

iex> FerricStore.unlock("lock:order:123", "worker_abc")
{:ok, 1}

xadd(key, fields)

@spec xadd(key(), [binary()]) :: {:ok, binary()} | {:error, binary()}

Appends an entry to the stream at key with an auto-generated ID.

fields is a flat list of field-value pairs: ["field1", "val1", "field2", "val2"]. Streams are append-only logs ideal for event sourcing, activity feeds, and audit trails.

Returns

  • {:ok, entry_id} where entry_id is a "timestamp-seq" string.
  • {:error, reason} on failure.

Examples

iex> FerricStore.xadd("events:user:42", ["action", "login", "ip", "10.0.0.1"])
{:ok, "1711234567890-0"}

iex> FerricStore.xadd("activity:feed", ["type", "comment", "body", "looks great!"])
{:ok, "1711234567891-0"}

xlen(key)

@spec xlen(key()) :: {:ok, non_neg_integer()}

Returns the number of entries in the stream at key.

Returns

  • {:ok, length} on success.

Examples

iex> FerricStore.xlen("events:user:42")
{:ok, 5}

xrange(key, start, stop, opts \\ [])

@spec xrange(key(), binary(), binary(), keyword()) :: {:ok, [tuple()]}

Returns entries from the stream at key in forward (oldest-first) order between start and stop.

Use "-" for the minimum and "+" for the maximum stream IDs.

Options

  • :count - Maximum number of entries to return.

Returns

  • {:ok, entries} where entries is a list of {id, [field, value, ...]} tuples.

Examples

iex> FerricStore.xrange("events:user:42", "-", "+", count: 10)
{:ok, [{"1711234567890-0", ["action", "login", "ip", "10.0.0.1"]}]}

iex> FerricStore.xrange("activity:feed", "-", "+")
{:ok, [{"1711234567891-0", ["type", "comment", "body", "looks great!"]}]}

xrevrange(key, stop, start, opts \\ [])

@spec xrevrange(key(), binary(), binary(), keyword()) :: {:ok, [tuple()]}

Returns entries from the stream at key in reverse (newest-first) order between stop and start.

Options

  • :count - Maximum number of entries to return.

Returns

  • {:ok, entries} where entries is a list of {id, [field, value, ...]} tuples.

Examples

iex> FerricStore.xrevrange("events:user:42", "+", "-", count: 5)
{:ok, [{"1711234567890-0", ["action", "login", "ip", "10.0.0.1"]}]}

xtrim(key, opts)

@spec xtrim(
  key(),
  keyword()
) :: {:ok, non_neg_integer()}

Trims the stream at key to a maximum number of entries, evicting the oldest.

Useful for capping event logs and activity feeds to prevent unbounded growth.

Options

  • :maxlen (required) - Maximum number of entries to keep.

Returns

  • {:ok, trimmed_count} - the number of entries removed.

Examples

iex> FerricStore.xtrim("events:user:42", maxlen: 1000)
{:ok, 5}

zadd(key, score_member_pairs)

@spec zadd(key(), [{number(), binary()}]) :: {:ok, non_neg_integer()}

Adds members with scores to the sorted set stored at key.

score_member_pairs is a list of {score, member} tuples where score is a number and member is a binary string. If a member already exists, its score is updated. Returns the count of new members added (not counting score updates).

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}])
{:ok, 2}

iex> FerricStore.zadd("leaderboard", [{150.0, "alice"}, {300.0, "charlie"}])
{:ok, 1}

zcard(key)

@spec zcard(key()) :: {:ok, non_neg_integer()}

Returns the number of members in the sorted set stored at key.

Returns {:ok, 0} if the key does not exist.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}])
iex> FerricStore.zcard("leaderboard")
{:ok, 2}

iex> FerricStore.zcard("nonexistent")
{:ok, 0}

zcount(key, min, max)

@spec zcount(key(), binary(), binary()) :: {:ok, non_neg_integer()}

Counts members in the sorted set at key with scores between min and max.

Use "-inf" and "+inf" for unbounded ranges. Prefix a bound with "(" for exclusive.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}, {300.0, "charlie"}])
iex> FerricStore.zcount("leaderboard", "100", "200")
{:ok, 2}

iex> FerricStore.zcount("leaderboard", "-inf", "+inf")
{:ok, 3}

zincrby(key, increment, member)

@spec zincrby(key(), number(), binary()) :: {:ok, binary()} | {:error, binary()}

Increments the score of member in the sorted set at key by increment.

Creates the member with the given increment as score if it does not exist. Returns the new score as a string.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}])
iex> FerricStore.zincrby("leaderboard", 50.0, "alice")
{:ok, "150.0"}

iex> FerricStore.zincrby("leaderboard", 25.0, "newcomer")
{:ok, "25.0"}

zmscore(key, members)

@spec zmscore(key(), [binary()]) :: {:ok, [float() | nil]}

Returns scores for multiple members in the sorted set at key.

Returns nil for members that do not exist. The order of returned scores matches the order of the input members.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}])
iex> FerricStore.zmscore("leaderboard", ["alice", "unknown", "bob"])
{:ok, [100.0, nil, 200.0]}

zpopmax(key, count \\ 1)

@spec zpopmax(key(), pos_integer()) :: {:ok, [{binary(), float()}]}

Removes and returns up to count members with the highest scores.

Returns {:ok, []} if the key does not exist or the sorted set is empty.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}, {300.0, "charlie"}])
iex> FerricStore.zpopmax("leaderboard", 1)
{:ok, [{"charlie", 300.0}]}

iex> FerricStore.zpopmax("leaderboard", 2)
{:ok, [{"bob", 200.0}, {"alice", 100.0}]}

zpopmin(key, count \\ 1)

@spec zpopmin(key(), pos_integer()) :: {:ok, [{binary(), float()}]}

Removes and returns up to count members with the lowest scores.

Returns {:ok, []} if the key does not exist or the sorted set is empty.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}, {300.0, "charlie"}])
iex> FerricStore.zpopmin("leaderboard", 1)
{:ok, [{"alice", 100.0}]}

iex> FerricStore.zpopmin("leaderboard", 2)
{:ok, [{"bob", 200.0}, {"charlie", 300.0}]}

zrandmember(key, count \\ nil)

@spec zrandmember(key(), integer() | nil) :: {:ok, binary() | [binary()] | nil}

Returns one or more random members from the sorted set at key.

Without count, returns a single member or nil for empty/nonexistent keys. With positive count, returns up to count unique members. With negative count, returns abs(count) members with possible duplicates.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}, {300.0, "charlie"}])
iex> {:ok, member} = FerricStore.zrandmember("leaderboard")
iex> member in ["alice", "bob", "charlie"]
true

iex> {:ok, members} = FerricStore.zrandmember("leaderboard", 2)
iex> length(members)
2

iex> FerricStore.zrandmember("nonexistent")
{:ok, nil}

zrange(key, start, stop, opts \\ [])

@spec zrange(key(), integer(), integer(), zrange_opts()) ::
  {:ok, [binary() | {binary(), float()}]}

Returns members in the sorted set stored at key within the rank range start..stop.

Indices are zero-based and inclusive. Negative indices count from the end (-1 is the last element). Members are ordered by score ascending.

Options

  • :withscores - When true, returns {member, score} tuples instead of bare member strings. Defaults to false.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}, {300.0, "charlie"}])
iex> FerricStore.zrange("leaderboard", 0, -1)
{:ok, ["alice", "bob", "charlie"]}

iex> FerricStore.zrange("leaderboard", 0, 1, withscores: true)
{:ok, [{"alice", 100.0}, {"bob", 200.0}]}

iex> FerricStore.zrange("nonexistent", 0, -1)
{:ok, []}

zrangebyscore(key, min, max, opts \\ [])

@spec zrangebyscore(key(), binary(), binary(), keyword()) :: {:ok, [binary()]}

Returns members with scores between min and max (inclusive by default).

Use "-inf" and "+inf" for unbounded ranges. Prefix a bound with "(" for exclusive (e.g., "(100" means score > 100).

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}, {300.0, "charlie"}])
iex> FerricStore.zrangebyscore("leaderboard", "100", "200")
{:ok, ["alice", "bob"]}

iex> FerricStore.zrangebyscore("leaderboard", "-inf", "+inf")
{:ok, ["alice", "bob", "charlie"]}

iex> FerricStore.zrangebyscore("leaderboard", "(200", "+inf")
{:ok, ["charlie"]}

zrank(key, member)

@spec zrank(key(), binary()) :: {:ok, non_neg_integer() | nil}

Returns the rank of member in the sorted set at key (ascending score order).

Rank is 0-based (the member with the lowest score has rank 0). Returns {:ok, nil} if the member or key does not exist.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}, {300.0, "charlie"}])
iex> FerricStore.zrank("leaderboard", "alice")
{:ok, 0}

iex> FerricStore.zrank("leaderboard", "charlie")
{:ok, 2}

iex> FerricStore.zrank("leaderboard", "unknown")
{:ok, nil}

zrem(key, members)

@spec zrem(key(), [binary()]) :: {:ok, non_neg_integer()}

Removes one or more members from the sorted set stored at key.

Members that do not exist are ignored. Returns the count of members actually removed.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}])
iex> FerricStore.zrem("leaderboard", ["alice"])
{:ok, 1}

iex> FerricStore.zrem("leaderboard", ["nonexistent"])
{:ok, 0}

zrevrank(key, member)

@spec zrevrank(key(), binary()) :: {:ok, non_neg_integer() | nil}

Returns the reverse rank of member in the sorted set at key (descending score order).

Rank is 0-based (the member with the highest score has rank 0). Returns {:ok, nil} if the member or key does not exist.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}, {300.0, "charlie"}])
iex> FerricStore.zrevrank("leaderboard", "charlie")
{:ok, 0}

iex> FerricStore.zrevrank("leaderboard", "alice")
{:ok, 2}

iex> FerricStore.zrevrank("leaderboard", "unknown")
{:ok, nil}

zscore(key, member)

@spec zscore(key(), binary()) :: {:ok, float() | nil}

Returns the score of member in the sorted set stored at key.

Returns {:ok, score} if the member exists, or {:ok, nil} if the member or the key does not exist.

Examples

iex> FerricStore.zadd("leaderboard", [{100.0, "alice"}, {200.0, "bob"}])
iex> FerricStore.zscore("leaderboard", "alice")
{:ok, 100.0}

iex> FerricStore.zscore("leaderboard", "unknown")
{:ok, nil}