Scalar: once per row
The plain form takes one row’s values and returns one result. Types are inferred from the annotations:pip declares the worker’s dependencies; they are part of the function’s
immutable version, so changing one produces a new version rather than
altering rows a previous version already filled.
Batch: once per call, many rows at a time
A function is called once per batch instead of once per row when its parameters are annotatedpyarrow.Array, or when it takes a single
pyarrow.RecordBatch parameter to read more than one column together. The
same doubling logic, vectorized over a whole batch at once:
batch_size caps the number of input rows in each execution batch; actual
batches may be smaller. row_timeout_seconds bounds each Python call: one
row for scalar Functions or the entire batch for vectorized Functions. It
does not include the instance’s __init__. Both settings are fixed into the
registered version and apply to direct calls, computed columns, and
materialized-view refreshes alike. Omit them to use adaptive batching and the
service’s default per-call deadline.
Stateful Functions: load once, call many times
A@function-decorated class is the same callable with state. Each remote
instance calls __init__ once, calls __call__ for every row or batch it
processes, and calls close(), if defined, once when it retires:
version(value=col("value"), factor=2.0). Load models, open clients, and
build rate limiters in __init__; every batch the instance serves then
reuses them, since an instance’s state is shared across its own batches but
never across other instances or workers. This is the pattern GPU inference
and remote-API Functions both build on below.
GPU: batch inference, loaded once
db.list_gpu_classes() lists the GPU classes configured for the deployment.
Each record reports the operator’s hardware description and limits; it does
not mean a GPU is currently free. Listing requires the System Usage
permission. Pass a returned class name to gpu_class to fix that placement
in the Function version. gpu=True remains required:
GET /v1/gpu/classes raise
NotImplementedError. Without gpu_class, the deployment’s default GPU
class is used. The class is immutable with the Function version; set a
per-refresh class or aggregate GPU budget through
refresh_column_async.
The model loads once per worker instance, in __init__; __call__ only
encodes the batch it was handed. Registering the same logic as a plain
function instead would reload the model on every single row.
Calling a remote service
A function that calls an external API needs a credential without putting it in the function’s source. A Secret is a database-scoped named credential:db.create_secret stores the value once, and binding it at registration time
puts it in the environment variable the function reads, without ever
returning it from any API or recording it in the function’s own definition.
Combine it with the stateful form so the HTTP client — and any rate limiter
around it — is built once, not reopened on every row. There is no built-in
retry or per-row error-skip policy — a function that calls a flaky service
should handle timeouts and retries itself, inside __call__.
See Vend a Secret to a Function for the
full worked example, how the bound value actually reaches a running
evaluation, and how that differs from rotating or revoking it later.
Read and produce blobs
A Function can bind a Blob v2 column and return another blob, including a blob nested inside a struct field, not only at the top level. In row mode, a top-level blob parameter arrives as Pythonbytes, or None for a nullable input, after the worker has read the
payload. Return bytes and let the destination table store the Managed
payload and descriptor.
This section extracts a frame from a stored video and captions it,
continuing as one example across chaining, persisting and incremental reads
below. videos holds raw clips in a video Blob v2 column; frames names
one timestamp per clip to extract, with a selected flag to skip some:
Produce a blob
Declare blob inputs and outputs explicitly.extract_frame reads one frame
out of a video at a timestamp and returns it as a JPEG, without loading the
whole clip first — see Read a blob incrementally below for
how the video: BlobReader parameter does that:
table.fetch_blobs("payload.image", hits).
A list or large-list output may also contain Blob v2 elements; inputs and
fixed-size lists do not support this layout. List-nested values are queryable
as descriptors, but fetch_blobs and blob_mode="bytes" cannot fetch their
payloads.
Chain a Function on its output
A second Function can bind a blob another Function produced. Registercaption the same way, consuming a blob instead of producing one:
video and ts from two different
tables, and caption reads extract_frame’s result as it is computed,
before anything is written:
EXPLAIN on a query like this shows RemoteTake: the
blob is read once, where the worker runs, and never pulled into the query
node first.
Persist it as a column, or a materialized view
Apply the same registered version withadd_columns to persist its output as
an ordinary table column, on a table that already has both of its inputs —
here, a single-clip table with its own video and ts, rather than the two
tables the join above reads from:
col("jpeg") directly, the same as the SQL join above does through
its own expression. Refresh the producer first, then the consumer. Once
outputs are current, a later refresh fills appended rows. A filtered
refresh preserves payload references for unselected Managed outputs. An
unfiltered refresh can recompute a whole fragment when it cannot establish
that the fragment’s outputs are current.
Function output blobs follow the same compaction and cleanup lifecycle as
ingested Managed blobs. Retained versions continue to own their payloads;
cleanup reclaims an object only after its retained references are gone.
Read a blob incrementally
Annotate a row Function parameter withBlobReader to read a Blob v2 input
incrementally, the way extract_frame above does, instead of materializing
the whole payload first. The signature selects the access mode without
another registration option. A minimal example, reading only the first MP4
box type without loading the rest of the video:
video.seekable() is True. read(size) reads from the current cursor;
read_range(offset, length) reads a byte range without moving it, the same
bytes a seek(offset) followed by read(length) would read. size()
returns the payload size without reading it. Each row call receives its own
reader, which closes when the call returns or raises. Do not retain it for
another invocation.
Use video: bytes when the Function needs the complete payload. Bytes inputs
materialize before invocation and remain subject to eager input limits.
Reader inputs keep the unread payload out of the input batch, but calling
video.read() without a size loads all remaining bytes in Python. Reader
inputs are supported in row mode. Return values remain ordinary values such
as bytes; this feature does not add streamed outputs.
What to read next
Function
List, describe and drop registered Function versions.
Vend a Secret to a Function
Bind a credential by reference instead of putting it in the function’s source.
Chunk records into multiple rows
A Function whose output is a list, exploded into one row per element.
Refresh a computed column
Filling the column a Function is bound to, and re-filling it as inputs change.