Cistern (cistern v0.1.1)
A Redix-backed Redis client with a poolboy-managed connection pool and
automatic type coercion on reads.
Features
- Connection pooling via
:poolboy(configurable size and overflow) - Automatic type coercion:
"true"/"false"→boolean, numeric strings →integer - High-level helpers:
get/1,set/3,multiple/1,set_many/2,delete/1,delete_many/1,increment/1 - Low-level escape hatches:
command/2andpipeline/2 - Fire-and-forget writes via
noreply_pipeline/2 - Composable iodata keys — pass
["prefix:", id]anywhere a key is accepted
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
@type result() :: {:ok, value()} | {:error, atom() | Redix.Error.t()}
@type value() :: Redix.Protocol.redis_value() | boolean() | integer()
Functions
@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.
@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
- See https://hexdocs.pm/redix/Redix.html#pipeline/3-options for more options.
Examples
iex> Cistern.command(["SET", "foo", "bar"])
{:ok, "OK"}
iex> Cistern.command(["GET", "foo"])
{:ok, "bar"}
iex> Cistern.command(["PING"])
{:ok, "PONG"}
@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}
@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}
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- Whenfalse, the raw string is returned without type coercion (e.g."01001"stays"01001"instead of becoming1001). Overrides the globalconfig :cistern, coerce: <bool>for this call. Defaults to the global config, ortrueif 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}
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"}}
@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- Whenfalse, each raw string is returned without type coercion (e.g."01001"stays"01001"instead of becoming1001). Overrides the globalconfig :cistern, coerce: <bool>for this call. Defaults to the global config, ortrueif 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]}
@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
- See https://hexdocs.pm/redix/Redix.html#pipeline/3-options for more options.
Examples
iex> Cistern.noreply_pipeline([["SET", "foo", "bar"], ["SET", "baz", "true"]])
:ok
@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
- See https://hexdocs.pm/redix/Redix.html#pipeline/3-options for more options.
Examples
iex> Cistern.pipeline([["SET", "foo", "bar"], ["SET", "baz", "true"]])
{:ok, ["OK", "OK"]}
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}
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
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