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

new(opts)

@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 of non_neg_integer/0, non_neg_integer/0 values) - {replicas, timeout_ms} for WAIT after each write (default: config :normandy, :redis_wait). With replicas, timeout_ms must be positive.

  • :wait_connections (pos_integer/0) - How many connections waited writes run on (default 4); only with a :wait of one replica or more.

  • :namespace (String.t/0) - Required. The key namespace.