Welcome to the new DOCS.vastdata.com. We've added new menus and a version selector, plus new persona and resource pages to help customers get started. Looking for a previous article? Use the docs search.

Architecture

Prev Next

Applies to VAST Cluster 5.5.

This page explains what VAST DataEngine runs where, the path one event takes from an S3 upload through your functions, what vastde pipelines deploy does, and how retries, scaling and telemetry work. You don't need it to run the quickstart, but read it before you size a pipeline or debug one that's stuck or dropping events.

What runs where

Where What runs there
VAST cluster: VMS DataEngine's control plane. It stores your functions, triggers and pipelines, checks and deploys pipelines, runs schedule triggers , and answers telemetry queries. vastde, the Web UI and the REST API all talk to it.
VAST cluster: your views Element events. When an object or file changes in a view that an element trigger watches, the cluster publishes a CloudEvent to the trigger's topic.
Event broker The topics: each trigger's topic, the tenant's default topic and its dead-letter topic. Usually a VAST Event Broker view, served by the cluster's CNodes; it can be an external Kafka broker.
Compute cluster Your functions. DataEngine's services, Knative Serving and Knative Eventing are installed there by your platform team. Each pipeline gets Kubernetes resources in its namespace: one autoscaling Knative service per function deployment, and the consumers and delivery rules that move events from topics to functions. The namespace is your tenant's DataEngine namespace, dataengine-ns-<tenant GUID>.
Compute cluster: telemetry collector Receives logs, traces and metrics from your functions and writes them to tables on the VAST cluster.
Container registry Your function images. The compute cluster pulls them when a pipeline deploys or scales up.

You use vastde, the Web UI or the REST API, which all talk to VMS on the VAST cluster. VMS is DataEngine's control plane: it stores functions, triggers and pipelines, deploys pipelines to the compute cluster, runs schedule triggers and answers telemetry queries. When an object or file changes in a view, the cluster publishes an element event to a topic on the event broker, which also holds the default and dead-letter topics. On the compute cluster, each pipeline's namespace has one autoscaling Knative service per function deployment, and consumers and delivery rules that move events from topics to functions over HTTP. The compute cluster pulls function images from the container registry. A telemetry collector on the compute cluster receives logs, traces and metrics and writes them to VAST DataBase tables, which VMS queries. Schedule triggers don't fire while the VMS is down.

VMS on the VAST cluster controls everything; your functions run on the compute cluster, fed from topics on the event broker.

Schedule triggers depend on VMS: in 5.5 they don't fire while the VMS is down, for example during an upgrade or an HA failover. Element triggers are unaffected.

The path of one event

Take a pipeline with an element trigger on bucket images, a function deployment thumbnail linked from it, and a function deployment index linked from thumbnail:

  1. An object lands. A client uploads images/cat.jpg over S3.
  2. The trigger publishes. The object's name passes the trigger's filters, so the cluster publishes a CloudEvent of type vastdata.com:Element.ElementCreated to the trigger's topic, with elementpath set to images/cat.jpg. Its data is an S3-style notification record for the upload. See events.
  3. The compute cluster consumes. In the pipeline's namespace, a consumer reads the trigger's topic, and the link's delivery rule sends the event over HTTP to one instance of thumbnail. If no instance is free, the service scales up.
  4. The handler runs. The DataEngine runtime in the container turns the request into a VastEvent and calls your handler(ctx, event). If the handler returns normally, the delivery succeeds. If it raises, the delivery fails and is retried (see retries).
  5. The return value becomes the next event. The runtime sends what the handler returned as a new CloudEvent of type vastdata.com:Function, whose source is thumbnail. It keeps the incoming event's subject, ID and partition key, and carries any labels the handler set. A handler that returns None still sends an event, with no data. If no link starts at the function, the return value is discarded.
  6. The next link routes it. The event travels on the link's topic (the default topic if the link names none). The delivery rule for the thumbnail → index link matches events from thumbnail that carry all of the link's labels, and sends them to index.
  7. The trace follows it. Each step adds spans to the event's trace, so vastde traces get <trace-id> and the UI's trace view show the whole path.

Nothing goes back to the original writer: the S3 upload completed at step 1. Element events are asynchronous.

Step 1: a client uploads images/cat.jpg over S3. Step 2: the trigger publishes an Element.ElementCreated CloudEvent to its topic, with elementpath images/cat.jpg. Steps 3 and 4: a consumer on the compute cluster delivers it over HTTP to an instance of thumbnail, which runs handler(ctx, event). Step 5: the return value is sent as a Function event on the link's topic, the default topic if the link names none. Step 6: index receives the events from thumbnail that carry all of the link's labels. If a handler raises or the call times out, the delivery is retried up to the link's retries with increasing waits, and the handler runs again; when the retries run out, the event goes to the dead-letter topic. Each link retries the same way. Step 7: every step adds spans to the event's trace. The upload completed at step 1: element events are asynchronous.

Each hop is a topic, a delivery with retries, and an HTTP call to a function instance.

If thumbnail is configured for batching, step 4 waits until enough events arrive or the batch timeout passes, then calls the handler once with the whole batch. Each item in the list it returns is sent on as its own event.

What deploy does

vastde pipelines deploy <pipeline> (or pipelines create --deploy) deploys the pipeline's latest revision:

  1. Checks. The revision must have at least one trigger, at least one link, and at least one link from a trigger. Every secret it names must exist in the pipeline's namespace, and every function's container registry must be linked to the pipeline's cluster and namespace. If a check fails, the command fails and the pipeline stays as it was; after pipelines create --deploy, that means the pipeline exists, in Draft. Other mistakes, such as a link that names an unknown function deployment, are caught earlier, when you create or update the pipeline. See the error reference.
  2. Resolves revisions. Each function deployment gets its pinned revision, or the function's default revision if it's unpinned. See which revision runs.
  3. Applies it to the compute cluster. VMS creates or updates the pipeline's Kubernetes resources in its namespace, and marks this revision as the deployed one.
  4. Status InProgress. The command returns once the request is accepted; Kubernetes is still pulling images and starting services, so check the status before you send events.
  5. Status Ready or Error. In the background, the cluster checks the pipeline's resources on the compute cluster and updates its status.
Draft ──deploy──▶ InProgress ──▶ Ready
                       │
                       └──────▶ Error (reason)

Ready means every part of the pipeline reports ready: the function services, the consumers on the trigger topics and the delivery rules for the links. Error means at least one doesn't, and the pipeline's reason carries that part's own message. For a function whose container exits at start-up, for example because main.py imports a package that isn't installed, the reason includes the container's output with the Python traceback. The pipeline stays InProgress for several minutes before it goes to Error, and vastde logs shows nothing for that function, because it never starts. See troubleshooting. If the pipeline's resources have disappeared from the namespace, the reason says the pipeline wasn't found there.

Read the status with vastde pipelines get <pipeline>, and the reason with vastde pipelines get <pipeline> -o yaml (the default output doesn't show it). A check that fails in step 1 fails the command itself, and nothing changes on the compute cluster.

Updating a pipeline doesn't touch what's running. The deployed revision keeps running until you deploy again. Deleting a pipeline sets it to Deleting, removes its resources from the compute cluster, and then removes the pipeline.

Retries and the dead-letter topic

Each link delivers events to its destination functions over HTTP. A delivery fails when the handler raises an exception, when the call times out, or when the runtime can't send the handler's result on to the next link.

  1. A failed delivery is retried, up to the link's retries setting, with a wait between attempts that grows each time. retries counts the deliveries after the first: with retries: 2, the handler ran three times. If you leave retries out, a server default applies, which can keep retrying a failing event for a long time before it's dead-lettered, so set retries explicitly on every link.
  2. When the retries run out, the event goes to the tenant's dead-letter topic, with the other function, routing and dispatching failures.

Every attempt runs in the same trace, so vastde traces list shows one trace with status ERROR that lasts from the first attempt to the last. Each attempt also logs an Error in handler execution: <message> record at ERROR.

Consequences for your code:

  • A handler can see the same event more than once. A retry calls the handler again, even if the first attempt did its work and only failed afterwards. Make handlers idempotent: write output under names derived from the input, use upserts, or check before you write.
  • Exceptions are your failure signal. Raise when you want the event retried and, eventually, dead-lettered. Catch and log when you don't.
  • Watch the dead-letter topic. It's a topic on the tenant's broker, so you can consume it with a Kafka client. Every pipeline in the tenant shares it. See the dead-lettered record.

On a 5.5 cluster, an event whose handler failed was delivered again, with the same id, on every retry, and then reached the dead-letter topic; no events were lost. Design for at-least-once delivery, and don't rely on events never being lost or never being repeated.

The dead-lettered record

A dead-lettered record is the original event, unchanged, written to the dead-letter topic as a Kafka record whose headers hold the CloudEvent attributes (ce_id, ce_type, ce_source, ce_subject, ce_time, the extensions such as ce_elementpath) and whose value is the original data. Three headers are added:

Header Contents
ce_knativeerrorcode The HTTP status of the last failed attempt, for example 500 when the handler raised
ce_knativeerrordata The failed attempt's response body, base64-encoded. When the handler raised, it's the exception's message.
ce_knativeerrordest The address of the function deployment that failed

For the handler above, which raised ValueError(f"cannot process {event.object_key}"), the record for fail/broken.txt had these headers, among others:

ce_id: 88051124535306
ce_type: vastdata.com:Element.ElementCreated
ce_elementpath: docs-src/fail/broken.txt
ce_knativeerrorcode: 500
ce_knativeerrordata: Y2Fubm90IHByb2Nlc3MgZmFpbC9icm9rZW4udHh0

ce_knativeerrordata decodes to the exception's message:

$ echo Y2Fubm90IHByb2Nlc3MgZmFpbC9icm9rZW4udHh0 | base64 -d; echo
cannot process fail/broken.txt

To replay or alert on dead letters, consume the topic with any Kafka client. The record identifies the event (ce_id, ce_source for the trigger, ce_elementpath for the object) but not the pipeline, so if several pipelines share a trigger, use ce_knativeerrordest or the trigger to tell them apart.

Ordering

A link's events_order decides ordering. With ordered, a destination processes one event per topic partition at a time, in order, so its concurrency is capped at the topic's partition count. With unordered, events are processed concurrently and can finish in any order. Events for the same object are expected to land in the same partition.

Scaling and concurrency

Each function deployment is its own Knative service, so each step of a pipeline scales on its own. You set the bounds in the pipeline YAML, under the deployment's resources:

  • Instances. min_concurrency and max_concurrency are the minimum and maximum number of instances (pods). The autoscaler adds and removes instances between them, using the deployment's autoscaling metric: concurrent requests (concurrency), requests per second (rps), or CPU or memory use (resource). If you don't give one, the cluster adds a concurrency-based block when it stores the pipeline. The autoscaling settings also have a scale-to-zero delay, for deployments allowed to scale down to no instances.
  • Requests per instance. max_concurrent_requests_limit caps how many requests one instance handles at once (Knative's container concurrency; 0 means no limit). If you leave it out, it's computed from num_workers and the batch size.
  • Threads. A synchronous (def) handler runs in a pool of num_workers threads, so one instance runs at most that many calls at once; it refuses calls beyond that. An async def handler runs on the instance's event loop and isn't limited by the pool.
  • CPU, memory, storage (min_cpu, max_cpu, min_memory, max_memory, storage) are per instance.
  • Start-up. init(ctx) runs once when an instance starts, and the instance gets no events until init has finished. Put slow set-up (loading a model, opening clients) in init, not in the handler.
  • Timeout. The deployment's timeout (seconds) limits how long one call can take. The runtime itself has no handler timeout. With batching, the timeout must be longer than the batch timeout.

An ordered link caps concurrency at the topic's partition count whatever these settings say. See pipeline YAML for every field and its limits.

Batching

With config.event_batching set on a function deployment (batch_max_size and batch_timeout_ms), the instance collects events and calls the handler with a list when the batch is full or the timeout passes, whichever comes first. The handler must return a list of the same length, and each item is sent downstream as its own event. A batch has its own trace, and each event in it keeps its own. See write a function.

Logs, traces and metrics

The runtime in each function instance sends telemetry over OpenTelemetry to DataEngine's telemetry collector on the compute cluster. The collector writes it into VAST DataBase tables on the VAST cluster, and VMS answers queries over them.

Signal What's captured Read it with
Logs ctx.logger, any Python logging logger, and print() (logged at INFO). Output to stderr that doesn't go through logging isn't captured. Each log has a scope: user for your code, runtime for the DataEngine runtime (shown as [vast-runtime] in vastde logs get output). vastde logs get, vastde logs tail; the UI's Logs page
Traces One trace per event, across every function it passes through, with spans for each call and any spans you add (ctx.tracer, @span_wrapped, @event_span_wrapped). Each instance's init call gets a trace too, so the first event on a new instance shows up as two traces. Batches have their own trace. vastde traces list, vastde traces get; the UI's trace views
Metrics Automatic metrics: dataengine.function.invocations, dataengine.function.invoke_duration, dataengine.event.total and dataengine.event.reception_latency (and dataengine.function.errors for failures ), plus counters, histograms, gauges and up-down counters you create from ctx. vastde metrics get, vastde metrics names; the UI dashboard

Telemetry is kept for a limited time, not forever. Copy anything you need to keep into your own tables.

vastde functions localrun runs your function in a local container that logs to the console only; nothing reaches the collector. See observability and build and test locally.

See also