Normandy.Behaviours.SessionStore.Redis
(normandy v2.2.0)
View Source
Durable SessionStore over Redis, modelled on Redis Streams. The conversation is an
append-only stream (XADD/XRANGE) so appends are atomic and O(1) with no
read-modify-write — concurrent appends never lose an entry. Session metadata
(turn state, config template, resume policy) lives in a hash; the ids of the sessions
with a turn in flight live in the SET "<namespace>:inflight" for list_resumable/1.
The handle is {conn, namespace}, {conn, namespace, wait} or
{conn, namespace, wait, wait_conns} (the host owns the Redix connections,
Oban-style; new/1 opens its own).
All of a session's keys share a {sid} hash-tag so multi-key reads/forks stay in one
Redis Cluster slot. Opaque values use term_to_binary / binary_to_term(_, [:safe]).
Durability is the operator's Redis config (AOF recommended). For the fail-closed
invariant, set wait: {numreplicas, timeout_ms} (new/1 option, or the third
handle element): every write (entry appends, turn state, config template) then
blocks on replica acknowledgement and returns {:error, {:wait_unmet, acked, n}}
if unmet. Without it, config :normandy, :redis_wait, {n, timeout_ms} applies;
the default {0, 0} does not wait. With replicas, timeout_ms must be positive:
Redis treats 0 as wait forever.
Waited writes and connections
WAIT blocks the connection it runs on until the replicas acknowledge that
connection's own earlier writes, so a write and its WAIT must share a connection,
and every command queued behind them on it waits too. wait_conns is a tuple of
Redix connections used for nothing else: a session's waited writes and their WAIT
run on elem(wait_conns, :erlang.phash2(session_id, tuple_size(wait_conns))), so one
session's writes stay in order and other sessions, reads and a registry sharing
conn do not queue behind its WAIT. new/1 with a replica :wait opens
:wait_connections of them. A handle without wait_conns runs waited writes on
conn.
Summary
Functions
Opens a Redix connection to :url and returns the store's handle: {conn, namespace},
plus the wait settings (and the WAIT connections) when :wait asks for replicas.
:url and :namespace are required: a host passes its own, and Normandy.Cluster
builds the handle of the Redis combo.
Functions
@spec new(keyword()) :: {pid(), binary()} | {pid(), binary(), {non_neg_integer(), non_neg_integer()}} | {pid(), binary(), {pos_integer(), pos_integer()}, tuple()}
Opens a Redix connection to :url and returns the store's handle: {conn, namespace},
plus the wait settings (and the WAIT connections) when :wait asks for replicas.
:url and :namespace are required: a host passes its own, and Normandy.Cluster
builds the handle of the Redis combo.
Options
:url(String.t/0) - Required. The Redis URL.:wait(tuple ofnon_neg_integer/0,non_neg_integer/0values) -{replicas, timeout_ms}forWAITafter each write (default:config :normandy, :redis_wait). With replicas,timeout_msmust be positive.:wait_connections(pos_integer/0) - How many connections waited writes run on (default4); only with a:waitof one replica or more.:namespace(String.t/0) - Required. The key namespace.