Skip to main content
Version: Next

Busyness and Backpressure

Zeta Engine collects vertex busyness and queue backpressure metrics for running jobs and displays their recent changes in the Web UI. These metrics help determine whether a performance bottleneck is more likely to be in a Source, TransformChain, Sink, or external system.

The active master retains only a short in-memory time series. This feature is intended for real-time troubleshooting and does not replace a long-term monitoring system such as Prometheus.

How It Works

Zeta observes a job from two perspectives:

  • Vertex busyness measures time spent reading from a Source, processing a Transform, or writing to a Sink.
  • Edge backpressure measures how long a producer waits to write to a bounded queue and how full the queue is.

The Zeta execution plan already isolates a Sink with an intermediate queue. For multiple Transforms combined in one TransformChain, an async boundary adds another queue so that processing capacity can be observed independently on each side of the boundary.

Enable Real-Time Metrics

Prerequisites

The Web UI and real-time REST API are served by the Zeta HTTP service. First enable HTTP in seatunnel.yaml:

seatunnel:
engine:
http:
enable-http: true
port: 8080

Real-time metrics are available only for running jobs and can be queried only from the active master.

Job Configuration

Enable observability in the job's env block:

env {
parallelism = 2
job.mode = "STREAMING"

engine {
observability {
enabled = true
bucket_ms = 5000
retention_minutes = 3
}
}
}
OptionDefaultDescription
enabledfalseEnables vertex and queue metric collection and aggregation.
bucket_ms5000Aggregation bucket length in milliseconds. Values below 1000 are raised to 1000.
retention_minutes3In-memory time-series retention. Values are clamped to the range from 1 to 10 minutes.
async_boundaries[]Transform names that start new TransformChains.
edge_buffer_capacity0Default async-boundary queue capacity. 0 or a negative value uses the engine default.
edge_overrides[]Overrides async-boundary queue capacity by boundary Transform name.

Explicitly setting enabled = true is recommended. If enabled is absent, a non-empty async_boundaries list or split_sink_io = true enables metric collection automatically. Explicit enabled = false keeps metric collection disabled and prevents configured async boundaries from being inserted.

split_sink_io is retained for configuration compatibility. It can trigger the automatic enablement described above, but it does not control Sink queue creation because the current execution plan already isolates each Sink with an intermediate queue. New jobs should enable metrics explicitly with enabled = true.

Configure an Async Boundary

Transform metrics are collected per TransformChain. When multiple Transforms are combined into one chain, only processing metrics for the entire chain are available. Add an async boundary where finer bottleneck location is required:

env {
engine {
observability {
enabled = true
async_boundaries = ["normalize_user", "enrich_user"]
edge_buffer_capacity = 2048
edge_overrides = [
{ boundary = "enrich_user", capacity = 8192 }
]
}
}
}

transform {
Sql {
name = "normalize_user"
plugin_input = "users"
plugin_output = "normalized_users"
query = "select * from users"
}
}

normalize_user becomes the start of a new TransformChain, and a bounded queue is inserted immediately upstream. Transform names are matched as exact strings, so explicitly configure a stable name for each boundary Transform.

Queue capacity is selected in the following order:

  1. Use the capacity in edge_overrides that matches the boundary Transform name.
  2. Otherwise, use edge_buffer_capacity.
  3. When the capacity is 0, use the engine default: 2048 for BlockingQueue and 1024 for Disruptor.

Negative edge_overrides entries are ignored, and values above 100000 are clamped to 100000. Other malformed entries are ignored with a warning. These capacity options affect only async-boundary queues and do not change the Sink-input queue.

A Disruptor capacity must be a power of two. Zeta rounds any other configured value up to the next power of two.

An async boundary changes the execution topology and adds one bounded queue for each parallel subtask. Use boundaries only where execution isolation or more precise bottleneck location is needed.

Vertex Metrics

The realtime REST API aggregates vertex metrics by DAG vertexId.

VertexMain fieldsDescription
SourcesourceReadRatio, sourceIdleRatioTime ratios spent reading records and performing empty polls or waits.
TransformtransformBusyRatio, transformProcessNsPerRecord, transformRecordsIn, transformRecordsOutProcessing time and input/output records for a TransformChain.
SinksinkBusyRatio, sinkWriteNsPerRecord, sinkRecordsInTime spent in writer.write() and the number of input records. prepareCommit, commit, and abort durations are returned in separate fields.

Vertex busyness uses the following calculation and is clamped to the range 0 to 1:

busy_ratio = elapsed_ns / (bucket_ns * subtask_count)

Vertex busyness is the proportion of an execution thread's time spent on the corresponding operation within a bucket. It is not CPU utilization.

Source sourceReadRatio is based on pollNext() duration. If a Source is directly connected to downstream operators, downstream logic executed synchronously in pollNext() can also contribute to the read time. Add an async boundary near the Source when Source and downstream processing time must be distinguished.

Edge Metrics

Backpressure metrics are available only on edges backed by an IntermediateQueue, including Sink-input edges and edges created by async boundaries.

FieldDescription
emitBlockedNsCumulative time in the current bucket during which producers wait for queue capacity.
bpRatioProducer wait time as a proportion of the bucket duration.
queueSizeSum of subtask queue lengths at the latest sample.
queueCapacitySum of subtask queue capacities.
queueFillRatioQueue fill ratio at the latest sample.

bpRatio is calculated as follows:

bp_ratio = emit_blocked_ns / (bucket_ns * subtask_count)

bpRatio shows whether a producer waits because the queue has no available capacity. queueFillRatio shows how full the queue is when sampled. A short traffic burst can fill a queue without continuously blocking its producer, so use both metrics and observe multiple buckets.

Locate a Bottleneck

Start from the edge nearest the Sink and work upstream. Add async boundaries only when a suspicious TransformChain must be narrowed down further.

ObservationLikely causeInvestigation
An input edge has persistently high bpRatio and queueFillRatio, and the downstream vertex also has high busynessThe downstream TransformChain or Sink cannot keep upCheck per-record processing time, external-system latency, and parallelism.
Source sourceIdleRatio is high and downstream queues remain emptyNo upstream data is available, or the Source is waiting for an external systemCheck upstream volume, polling intervals, and throttling settings.
Source sourceReadRatio is high, but the first observable edge is not backpressuredSource reading, deserialization, or a synchronous downstream chain is slowAdd an async boundary close to the Source and observe again.
Transform transformBusyRatio is high, and its input edge remains full and backpressuredThe TransformChain is a bottleneck candidateSplit the chain, inspect expensive expressions, or increase parallelism.
Sink sinkWriteNsPerRecord is high, and its input edge remains full and backpressuredThe external target system is slowCheck batching, flushing, retries, network latency, and target-system load.

Real-time metrics identify bottleneck candidates; they do not prove root cause on their own. Confirm the diagnosis with throughput, error logs, checkpoint status, and external-system monitoring.

Web UI

Open Running Jobs in the Web UI and select the Overview of a running job:

  • Vertex color represents the latest busyness of the Source, Transform, or Sink.
  • Edge color represents the latest bpRatio: gray at 0, green between 0 and 5%, yellow from 5% to 20%, orange from 20% to 50%, and red at 50% or higher.
  • Edge width increases with queueFillRatio.
  • Select a vertex or edge to view its recent time series and detailed fields.

The current UI queries the latest 3 minutes and refreshes every 2 seconds. The master collects metrics every 5 seconds, so consecutive UI refreshes can show the same data. The REST API supports query windows of up to 10 minutes.

For complex multi-input DAGs, the UI currently maps a queue to an edge primarily through targetVertexId. If several input queues target the same vertex, the UI might not uniquely represent every physical edge. Use the queueId values in the REST response to distinguish them.

REST API

The active master serves real-time endpoints under /metrics/realtime:

# List running jobs and their observability status
curl http://<master-host>:8080/metrics/realtime/jobs

# Query edge metrics for the latest 3 minutes
curl "http://<master-host>:8080/metrics/realtime/jobs/<jobId>/edges?windowMs=180000"

# Query vertex metrics for the latest 3 minutes
curl "http://<master-host>:8080/metrics/realtime/jobs/<jobId>/vertices?windowMs=180000"

windowMs defaults to 180000 and has a maximum of 600000. See RESTful API V2 for complete request and response schemas.

Performance Cost and Limitations

  • Workers maintain vertex and queue counters and measure producer wait time when a queue is full.
  • The active master fetches selected metrics for enabled jobs at a fixed, non-configurable 5-second interval and creates in-memory buckets.
  • Each async boundary adds queue memory and cross-thread transfer overhead.
  • Only short in-memory windows for running jobs are retained. Persistence, historical reports, and alerting are not provided.
  • A new active master does not restore the previous real-time window.
  • Transform metrics are reported per TransformChain.
  • Queue length and capacity are the last sampled values in a bucket, not averages or peaks.
  • Estimated ratios can fluctuate around job recovery, rescaling, or counter resets.