Cistern (cistern v0.1.1)

A Redix-backed Redis client with a poolboy-managed connection pool and automatic type coercion on reads.

Features

Configuration

Add the following to config/config.exs (or an environment-specific file):

config :cistern,
  host: "localhost",
  port: 6379,
  password: "",           # omit or leave blank for no auth
  pool_size: 10,          # number of persistent connections
  pool_max_overflow: 5,   # extra connections allowed under load
  pool_timeout: 5_000,    # ms to wait for a free connection
  sync_connect: true,
  exit_on_disconnection: true,
  coerce: true            # global read coercion; override per call with `coerce:`

Starting under a supervision tree

children = [
  Cistern,
  # ...other children
]

Supervisor.start_link(children, strategy: :one_for_one)

Quick example

iex> Cistern.set("counter", 0)
{:ok, 0}
iex> Cistern.increment("counter")
{:ok, 1}
iex> Cistern.get("counter")
{:ok, 1}
iex> Cistern.delete("counter")
{:ok, 1}

Summary

Functions

Returns a child spec to use Cistern in supervision trees.

Wrapper to call Redix.command/3 inside a poolboy worker.

Removes cache for a single key. A key is ignored if it does not exist. Returns {:ok, 1} when the key existed, {:ok, 0} otherwise.

Removes cache for a list of specified keys. A key is ignored if it does not exist. Returns a count of deleted keys.

Returns one cached value by a key. Redis stores any term as a string. The returned value will be parsed to a possible type

Increments the number stored at key by one. See Redis docs https://redis.io/commands/incr/ for more details.

Returns multiple cached values by a list of keys. For every key that does not hold a string value or does not exist,the special value nil is returned.

Wrapper to call Redix.noreply_pipeline/3 inside a poolboy worker.

Wrapper to call Redix.pipeline/3 inside a poolboy worker.

Stores a value in Redis cache under a given key. If key already holds a value, it is overwritten, regardless of its type. Any previous time to live associated with the key is discarded on successful SET operation.

Stores multiple key-value pairs in the cache.

Starts a pool of connections to Redis.

Types

result()

@type result() :: {:ok, value()} | {:error, atom() | Redix.Error.t()}

value()

@type value() :: Redix.Protocol.redis_value() | boolean() | integer()

Functions

child_spec(args \\ [])

@spec child_spec(args :: keyword()) :: Supervisor.child_spec()

Returns a child spec to use Cistern in supervision trees.

To use Cistern with the default options (same as calling Cistern.start_link()):

children = [
  Cistern,
  # ...
]

No options are supported for now. All options should be set in the config file.

command(args, opts \\ [])

@spec command(args :: list(), opts :: Keyword.t()) ::
  {:ok, Redix.Protocol.redis_value()} | {:error, atom() | Redix.Error.t()}

Wrapper to call Redix.command/3 inside a poolboy worker.

Options

Examples

iex> Cistern.command(["SET", "foo", "bar"])
{:ok, "OK"}
iex> Cistern.command(["GET", "foo"])
{:ok, "bar"}
iex> Cistern.command(["PING"])
{:ok, "PONG"}

delete(key)

@spec delete(key :: any()) ::
  {:ok, non_neg_integer()} | {:error, atom() | Redix.Error.t()}

Removes cache for a single key. A key is ignored if it does not exist. Returns {:ok, 1} when the key existed, {:ok, 0} otherwise.

The key may be a binary or iodata (e.g. ["prefix", id]); the same iodata interpretation used by get/1, set/3 and increment/1 applies. For deleting multiple distinct keys in a single round-trip, use delete_many/1.

Examples

iex> Cistern.delete("some_key")
{:ok, 0}

delete_many(keys)

@spec delete_many(keys :: [any()]) ::
  {:ok, non_neg_integer()} | {:error, atom() | Redix.Error.t()}

Removes cache for a list of specified keys. A key is ignored if it does not exist. Returns a count of deleted keys.

Each element is validated like a key passed to get/1 (binary or iodata).

Examples

iex> Cistern.delete_many(["foo", "boo"])
{:ok, 2}
iex> Cistern.delete_many([])
{:ok, 0}

get(key, opts \\ [])

@spec get(key :: any(), opts :: Keyword.t()) :: result()

Returns one cached value by a key. Redis stores any term as a string. The returned value will be parsed to a possible type:

  • "true" and "false" to a boolean
  • string number to an integer, ex: "123" to 123
  • regular strings will remain unchanged

Options

  • :coerce - When false, the raw string is returned without type coercion (e.g. "01001" stays "01001" instead of becoming 1001). Overrides the global config :cistern, coerce: <bool> for this call. Defaults to the global config, or true if unset.

Examples

iex> Cistern.set_many([{"foo", "bar"}, {"boo", "true"}, {"baz", "1"}])
:ok
iex> Cistern.get("foo")
{:ok, "bar"}
iex> Cistern.get("boo")
{:ok, true}
iex> Cistern.get("baz")
{:ok, 1}
iex> Cistern.get("baz", coerce: false)
{:ok, "1"}
iex> Cistern.get("some")
{:ok, nil}

increment(key)

@spec increment(key :: any()) :: result()

Increments the number stored at key by one. See Redis docs https://redis.io/commands/incr/ for more details.

Examples

iex> Cistern.set_many([{"baz", 1}, {"other", 0}, {"foo", "bar"}])
:ok
iex> Cistern.increment("baz")
{:ok, 2}
iex> Cistern.increment("other")
{:ok, 1}
iex> Cistern.increment("foo")
{:error, %Redix.Error{message: "ERR value is not an integer or out of range"}}

multiple(keys, opts \\ [])

@spec multiple(keys :: [any()], opts :: Keyword.t()) ::
  {:ok, nil | [value()]} | {:error, atom() | Redix.Error.t()}

Returns multiple cached values by a list of keys. For every key that does not hold a string value or does not exist,the special value nil is returned.

Options

  • :coerce - When false, each raw string is returned without type coercion (e.g. "01001" stays "01001" instead of becoming 1001). Overrides the global config :cistern, coerce: <bool> for this call. Defaults to the global config, or true if unset.

Examples

iex> Cistern.set_many([{"foo", "bar"}, {"boo", "true"}])
:ok
iex> Cistern.multiple(["foo", "boo"])
{:ok, ["bar", true]}
iex> Cistern.multiple(["foo", "boo"], coerce: false)
{:ok, ["bar", "true"]}
iex> Cistern.multiple([])
{:ok, nil}
iex> Cistern.multiple(["any"])
{:ok, [nil]}

noreply_pipeline(args, opts \\ [])

@spec noreply_pipeline(args :: list(), opts :: Keyword.t()) ::
  :ok | {:error, atom() | Redix.Error.t() | Redix.ConnectionError.t()}

Wrapper to call Redix.noreply_pipeline/3 inside a poolboy worker.

Note: Redix.noreply_pipeline/3 issues CLIENT REPLY OFF/ON under the hood. If your Redis server (or a proxy in front of it) does not support the CLIENT command, use pipeline/2 instead.

Options

Examples

iex> Cistern.noreply_pipeline([["SET", "foo", "bar"], ["SET", "baz", "true"]])
:ok

pipeline(args, opts \\ [])

@spec pipeline(args :: [list()], opts :: Keyword.t()) ::
  {:ok, [Redix.Protocol.redis_value()]}
  | {:error, atom() | Redix.Error.t() | Redix.ConnectionError.t()}

Wrapper to call Redix.pipeline/3 inside a poolboy worker.

Options

Examples

iex> Cistern.pipeline([["SET", "foo", "bar"], ["SET", "baz", "true"]])
{:ok, ["OK", "OK"]}

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

@spec set(key :: any(), value :: any(), opts :: Keyword.t()) :: result()

Stores a value in Redis cache under a given key. If key already holds a value, it is overwritten, regardless of its type. Any previous time to live associated with the key is discarded on successful SET operation.

Options

  • :ttl - An expiration time to set for the provided key (time-to-live), this value should be in milliseconds.

Examples

iex> Cistern.set("foo", "bar", ttl: 10_000)
{:ok, "bar"}
iex> Cistern.set("baz", 1)
{:ok, 1}
iex> Cistern.set("boo", true)
{:ok, true}

set_many(kv_list, opts \\ [])

@spec set_many(Keyword.t(), opts :: Keyword.t()) :: :ok | {:error, term()}

Stores multiple key-value pairs in the cache.

Options

  • :ttl - An expiration time to set for the provided keys, one for all, this value should be in milliseconds.

Examples

iex> Cistern.set_many([{"foo", "bar"}, {"boo", "true"}])
:ok
iex> Cistern.set_many([])
:ok

start_link(opts \\ [])

@spec start_link(opts :: keyword()) :: {:ok, pid()} | {:error, term()}

Starts a pool of connections to Redis.

This function returns {:ok, pid} if the Poolboy and Redix are started successfully.

The connection options should be set in the config file.

config :cistern,

host: "localhost",
port: 6379,
pool_size: 10,
pool_max_overflow: 5,
pool_timeout: 5_000,
password: "",
sync_connect: true,
exit_on_disconnection: true