R7.02.3 · dive 3
A bounded structure cannot leak.
A compact encoding saves bytes on a structure; a cap bounds how many bytes it can ever take. A List or Stream that grows without limit is a memory leak waiting for traffic.
The fix is to trim it where it is written: LTRIM list 0 N keeps a List to its
newest N; XTRIM…MAXLEN ~ keeps a Stream to roughly its newest N. EchoMQ does both —
the per-group wake list is capped at 64, the streams are MAXLEN ~-capped — so the ceiling is
a property of the code, not a hope about volume. Step the list past the cap; watch LTRIM clamp
it.
LTRIM list 0 63 keeps
at most 64 and discards the rest, so the size is fixed no matter the traffic.The unbounded structure is the hazard
memory-optimization.md.txt · the leakLists and Streams are append-friendly: LPUSH/RPUSH and XADD are O(1),
so it is easy to write to them on a hot path and never remove. Left alone, they grow with traffic forever, and
a structure that only grows will eventually exhaust memory — at which point, under noeviction,
writes start failing. A cap removes the hazard at the source: bound the structure when you write it, and its
size is fixed no matter how much traffic arrives.
A cap is also when a List or Stream leaves its compact encoding behind safely. The point of a cap is not the encoding, though — it is the absolute ceiling: a capped structure cannot leak, by construction.
An append-only structure with no cap is a memory leak waiting for traffic. Bound it where it is written and its size is fixed.
LTRIM — the wake list at 64
echo_mq/lanes.ex · the bounded ring
EchoMQ's fair-lanes layer keeps a per-group wake list: a short signal list a parked consumer watches, written on the hot admission path. If it were unbounded it would grow with every admitted job. It is not — it is capped the moment it is pushed:
-- the grouped-admission script, the wake bookkeeping
redis.call('LPUSH', KEYS[7], '1')
redis.call('LTRIM', KEYS[7], 0, 63)
LPUSH adds a marker; LTRIM KEYS[7], 0, 63 immediately keeps only indices 0 through
63 — at most 64 entries — and discards the rest. The list is a bounded ring, never an unbounded
log: one wake is enough to rotate a parked consumer back to service, so 64 markers is generous headroom and
the memory is fixed. The moduledoc frames the layer: “the ring is the rota — a list holding
exactly the lanes that can be served right now.”
Notes on ValkeyLTRIM key start stop keeps only the elements
in the inclusive range and removes the rest, so LPUSH then LTRIM 0 63 is the idiom for a
bounded ring of at most 64 entries —
valkey.io/commands/ltrim.
The cheapest cap is a fixed-length ring: LPUSH the new entry, LTRIM 0 N
to keep the newest N. EchoMQ's wake list is one such ring, bounded at 64 where it is written.
XTRIM…MAXLEN ~ — the stream cap
echo_mq/stream.ex · trim/4 · the approx trade
A Stream is the retained, replayable log. EchoMQ trims it to a declared retention window over
XTRIM, issued directly:
# the maxlen window — XTRIM issued direct, no new Lua
def trim(conn, queue, name, {:maxlen, count, approx?})
when is_binary(queue) and is_binary(name) and is_integer(count) and count >= 0 and
is_boolean(approx?) do
key = stream_key(queue, name)
xtrim(conn, ["XTRIM", key, "MAXLEN", approx_flag(approx?), Integer.to_string(count)])
end
defp approx_flag(true), do: "~"
defp approx_flag(false), do: "="
The third argument, approx?, selects the trim mode — and it is the memory-vs-exactness
trade. Select a mode to see where each trims:
MAXLEN ~ vs MAXLEN =. Select a mode; the readout reports
where the trim boundary falls and what the trade buys.true selects ~ (the safe default): Valkey trims in whole
macro-node boundaries, which is cheaper, may under-trim (the stream can briefly hold a few
more than count), but never over-trims — it can never delete an entry
inside the window. false selects =: trim exactly to the window edge, at a higher
cost. Either way the blast radius is bounded by the window, and trim/4 answers
{:ok, removed_count} — the integer XTRIM returns. The ~ form is the
right default precisely because the goal is a bound, not an exact count.
Bound a structure so it cannot leak: LTRIM list 0 N keeps a List to its newest N;
XTRIM…MAXLEN ~ keeps a Stream to roughly its newest N, trading exactness for a cheaper
trim.
The per-group wake list is LTRIM KEYS[7], 0, 63-capped at 64 (lanes.ex); the
streams are XTRIM…MAXLEN ~-capped (stream.ex, with ~ the safe
default). The memory ceiling is fixed in the code that writes the structure.
The cheapest guarantee against a memory leak is a cap at the write. EchoMQ caps the wake list at
64 and trims its streams with the approximate MAXLEN ~, so neither structure can grow without bound
— exactness given up only where a bound, not a precise size, is what matters.
The same record, under noeviction
A cap stops a structure leaking; noeviction governs what happens when memory is exhausted
anyway — the write fails loudly rather than a key vanishing.
Redis as a primary database grounds that
posture in the same job HASH this module keeps compact.
The maxmemory 512mb guardrail
Caps keep individual structures bounded; maxmemory 512mb is the loud ceiling on the whole
keyspace. The committed config and its production posture — Valkey on a Fly machine — is the
Branded Component System production chapter.
References
sources · relatedSources
- Valkey — LTRIM — keep a List to a range so it becomes a bounded ring; the wake-list cap at 64.
- Valkey — XTRIM — trim a Stream to
MAXLEN; the~flag trims in whole macro-nodes, never inside the window. - Valkey — XADD — append with an inline
MAXLEN ~cap on the same write. - Valkey — Introduction to Streams — the retained log, capped retention, and the approximate-trim trade.
Related in this course
- R7.02 · Memory optimizationthe module hub
- R7.02.1 · listpack and intsetthe compact encodings a small structure earns
- R7.02.2 · short field nameskeeping the row in the compact encoding
- R7.01 · Redis as a primary databasethe same job HASH, under
noeviction