bv.most_recent_n
Circular buffer of the N most recent values.
nis a required register-time kwarg per V0-MEM-GOV-02.
Signature
bv.most_recent_n(
field: str,
*,
n: int, # REQUIRED — register-time kwarg
where: bv.Col | None = None,
) -> AggDescriptor
Description
bv.most_recent_n returns the most recent n non-null values of field,
in insertion order (oldest at index 0, newest at index n − 1). State is
a Vec<Value> of capacity n plus a head index — a fixed-size circular
buffer. Once n events have arrived, the buffer is filled = true and
each subsequent event overwrites the value at head, then advances head
modulo n. Use it for "the last 10 IPs this account logged in from",
"the last 5 device fingerprints", or "the last 20 transaction amounts on
this card" — features that need a rolling sample without summarization.
n is a required keyword argument per
V0-MEM-GOV-02: the lifetime-aggregation
memory contract requires every unbounded-by-default operator to declare a
finite per-entity ceiling at register time. bv.most_recent_n's ceiling
is exactly n × sizeof(Value) bytes. The register-time JSON-prelude shim
(pre_check_unbounded_op_in_lifetime_mode) rejects any most_recent_n
payload missing n with the structured error code
unbounded_op_in_lifetime_mode. There is no fallback default — picking
n is a deliberate capacity-planning step. n is clamped to ≥ 1 at
state construction.
bv.most_recent_n belongs to the bounded-buffer family. Per-event
update is Tier 3 (~12 ns floor / ~32 ns measured per
cost-class.md) — one Value::clone() plus one indexed
write into the ring. The clone-path variance dominates: Value::Str clone
is Arc::clone (atomic bump, cheap); Value::Bytes clone can be expensive
for large payloads. There is no window= kwarg in v0 —
bv.most_recent_n is lifetime-only. For "last N matching values within
a window", compose with @bv.event(cold_after="...") per
V0-MEM-GOV-01, or use
bv.last_n — the point/ordinal sibling — if
you only need scalar values without the buffer-family insertion-order
guarantees.
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
field |
str |
Yes | — | Name of the field whose last n values to track. Any scalar Value type — i64, f64, str, bool, bytes. |
n |
int |
Yes | — | Number of values to retain. Must be ≥ 1 per V0-MEM-GOV-02 BoundedByRequiredKwarg("n"). Bounds the per-entity memory ceiling at register time. |
where |
bv.Col |
No | None |
Boolean expression on event fields; only matching events update the ring. |
Returns
A list of up to n values in arrival order (oldest at index 0, newest at
index n − 1). Wire form is Value::List — Python SDK readers receive a
native list. When the buffer is not yet filled (< n events seen), the
list is the partial buffer in arrival order. Cold-start (no events)
returns the empty list [] — never null.
Complexity
| Resource | Bound |
|---|---|
| CPU per event | Tier 3 (~12 ns floor / ~32 ns measured — circular-buffer write + one Value::clone()) — see cost-class.md. Clone-path variance: Value::Str is Arc::clone (cheap); Value::Bytes of large payloads can dominate |
| Memory per entity | BoundedByRequiredKwarg("n") — n × sizeof(Value) bytes per Phase 12.8 V0-MEM-GOV-02 |
| Lifetime mode | Required — bv.most_recent_n has no window= kwarg in v0; lifetime is the only mode |
Examples
Example 1: Last 10 IPs per user
import beava as bv
@bv.event
class Login:
user_id: str
ip_address: str
@bv.table(key="user_id")
def UserRecentIps(logins) -> bv.Table:
return (
logins.group_by("user_id")
.agg(recent_ips=bv.most_recent_n("ip_address", n=10))
)
# After 12 logins from various IPs
result = app.get("UserRecentIps", "alice")
# result == {"recent_ips": ["10.0.0.3", "10.0.0.5", ..., "10.0.0.7"]}
# Length 10 — the 2 oldest IPs were rotated out.
Example 2: Last 5 successful transaction amounts
@bv.table(key="card_id")
def CardRecentSuccess(txns) -> bv.Table:
return (
txns.group_by("card_id")
.agg(recent_amounts=bv.most_recent_n("amount",
n=5,
where=bv.col("status") == "captured"))
)
Wire
JSON wire form in a register payload:
{
"kind": "derivation",
"name": "UserRecentIps",
"output_kind": "table",
"key": ["user_id"],
"agg": {
"recent_ips": {
"op": "most_recent_n",
"params": {
"field": "ip_address",
"n": 10
}
}
}
}
See examples/wire/register-fraud-team.request.json for a full payload example.
Edge cases
nmissing at register time: rejected with structured error codeunbounded_op_in_lifetime_modeper V0-MEM-GOV-02. The JSON-prelude shim catches this before any state is allocated.n=0or negativen: clamped to1at state construction (n.max(1)), but the SDK helper rejects pre-wire withaggregation_invalid_param.- Fewer than
nevents seen: returns the partial list in arrival order (e.g.["a", "b"]after 2 events whenn=10). The buffer isfilled = false. - Empty stream / cold-start: returns
[](empty list) — nevernull. - Null source field (
Value::Null): events whosefieldisnullare skipped and do not consume buffer slots. - Missing source field: events without
fieldare skipped — no slot consumed. where=filter excludes everything: returns[]until matching events arrive.window=kwarg attempted: raisesTypeErrorat SDK-helper-call time. For a sliding-window analogue use@bv.event(cold_after="...")to bound the lifetime via per-entity TTL.- Large
Value::Bytescost: the per-event clone copies the bytes; for high-throughput workloads with large payloads, consider tracking a hash or a derived id rather than the raw bytes. - Out-of-order event-time: does not matter. beava is processing-time-only per
project_redis_shaped_no_event_time_ever; the buffer tracks server arrival order. - Lifetime mode: the only mode. Per-entity ceiling is
n × sizeof(Value)bytes per V0-MEM-GOV-02 BoundedByRequiredKwarg("n").
See also
- cost-class.md — performance tier (Tier 3)
- bv.last_n — point/ordinal sibling (also
BoundedByRequiredKwarg("n")— chooses between by your traceability bucket) - bv.first_n — first-N companion (locks the first
nmatching values; never rotates) - bv.reservoir_sample — uniform-sample sibling (samples across the entire history rather than retaining the most recent
n) - bv.lag — single-value
n-events-ago companion (no buffer) - V0-MEM-GOV-02 —
BoundedByRequiredKwargmemory governance contract - pipeline-dsl/compilation-rules.md — chain compilation rules