Some workloads need the same client to keep hitting the same backend - a shopping cart in server memory, say. Sticky selection remembers a key-to-backend mapping so a given key routes stably, even as the pool changes around it.
Route each session key to a remembered backend, picking a fresh one only for keys not seen before.
Sticky sessions (session affinity) keep a client pinned to one backend so any
per-session state living on that server stays reachable. The mechanism is a
session table: a map from a session key to the id of the backend it was first
routed to. On a repeat request for the same key you return the remembered backend
directly, without consulting the underlying algorithm at all - which is why the
second u1 call does not advance the round-robin cursor.
The property that makes this genuinely sticky is stability under change. Because
the mapping is stored by id, adding, removing, or reordering an unrelated backend
leaves an existing session’s entry untouched - u1 keeps landing on A after D
joins. That is deliberately different from hashing the key modulo the pool size,
where adding a backend reshuffles almost every key; a full consistent-hashing ring
that minimizes that reshuffle is its own separate project. The one time a key does
re-pick is when its remembered backend is gone or unhealthy, which the lookup guards
with the IsUp check.
type Sticky struct { pool *Pool; under Selector; table map[string]string }func (s *Sticky) SelectFor(key string) (*Backend, error) {if id, ok := s.table[key]; ok {if b := s.pool.Get(id); b != nil && b.IsUp() { return b, nil }}b, err := s.under.Select() // first time (or pinned backend gone): pick freshif err != nil { return nil, err }s.table[key] = b.IDreturn b, nil}