Queue model
A Lakeshore queue is one record carrying two jobs. It is a task queue — the thing invocations line up in and workers pull from — and it is a launch queue — the policy deciding how many workers should exist. They are one model because the depth of the first is the input to the second.
The queue name you write in @udf(queue="gpu") is denormalized onto every
invocation, and the control plane reads that one string twice: once to route a
claim to a worker, and once to count how many invocations are still waiting.
Counting the backlog is how the autoscaler knows whether to grow the pool.
Splitting scheduling and provisioning into separate objects would mean keeping
that number in sync across both.
Read the two halves separately:
- Task queue — the behaviors: submit, claim, complete, retry, stream.
- Elastic queues — what attaching a provider adds.
The type
Naming and the prefix
A queue name is never global. The record carries a namespaceId, and the
uniqueness constraint is on the pair:
So the namespace is the prefix. Two namespaces can both own a queue called
gpu and they are different queues; within one namespace the name is the
identity, which is what lets @udf(queue="gpu") be a bare string in user code —
the namespace comes from the caller's credentials, not from the literal.
Everything keyed off a queue inherits that scoping. The idempotency key on a
submit (_key=) is unique per namespace + queue, not per queue name alone,
so the same key in two namespaces is two distinct invocations.
There is no separate prefix field, and name is a flat string — Lakeshore
does not parse team/gpu into a hierarchy today. If you want grouping inside
a namespace, it lives in the name by convention and nothing enforces it.
This differs from a Redis-backed queue like
zaku, where the prefix is a literal key
prefix (Zaku-task-queues:{queue}:pending) chosen at client construction. In
Lakeshore the equivalent partitioning is a control-plane relation, not a
string you assemble.
Creating one
You rarely have to: submitting to a queue name that does not exist creates it,
with kind defaulting to fifo. Reach for queues add when you want a
non-default discipline or a launch policy.
The CLI takes the dashed forms above (fully-elastic); the REST API and
compose files take underscores (fully_elastic). The CLI translates between
them.
Stored ahead of enforced
Several fields above are persisted and validated but not yet acted on, and the gaps are not visible from the field names:
| Field | Reality today |
|---|---|
kind | The hosted control plane dispatches by priority then oldest-first regardless of the value. The four disciplines are implemented in the SDK's local plane. |
admission | Validated and persisted, not applied at submit time. |
reservedSlots | Carried over from the collapsed Lane model; no scheduler reads it. |
customScalerUdf | Reserved for a later phase; the built-in scaler is the only one that runs. |
Check the reference before designing around any single field.
The behaviors: submit, claim, complete, retry, stream — and the state a task moves through.
What providerRef adds: scaling policies, the controller tick, and the
event feed.
The full CLI surface and the current state of each field.
What providerRef points at, and which provider types can launch today.