Skip to content

OpenTelemetry

Configure W3C trace-context and baggage propagation before you enqueue tasks or start workers:

fn main() {
    pgtask::otel::configure_propagation();

    // Install your OpenTelemetry tracer and meter providers before starting pgtask.
}

pgtask uses the global OpenTelemetry providers. It does not select an exporter or telemetry backend. This keeps the engine compatible with OTLP collectors, managed observability services, and application-specific sampling.

Set service.name, service.version, deployment.environment, and a unique service.instance.id on the OpenTelemetry resource before you start a worker. Configure your tracer and meter providers before constructing Worker, and shut them down when the process exits.

Traces

The engine creates these spans:

Span Kind Purpose
pgtask.enqueue Producer Enqueue one task and inject its trace context
pgtask.enqueue_many Producer Enqueue a batch with one shared producer context
pgtask.claim Internal Claim a bounded group of due tasks
pgtask.execute Consumer Execute one attempt with the producer context as its parent

Trace context is stored in the task headers JSON object. Existing application headers are preserved. The default propagators write traceparent, tracestate, and baggage when those values exist.

Metrics

Instrument Type Unit
pgtask.tasks Counter Tasks
pgtask.lease.renewals Counter Renewals
pgtask.queue.latency Histogram Seconds
pgtask.execution.duration Histogram Seconds
pgtask.schedule.occurrences Counter Tasks
pgtask.schedule.skipped_occurrences Counter Occurrences
pgtask.schedule.lag Histogram Seconds
pgtask.schedule.materialization.duration Histogram Seconds
pgtask.workers.live Gauge Workers
pgtask.worker.heartbeats Counter Heartbeats
pgtask.queue.ready.tasks Gauge Tasks
pgtask.queue.unroutable.tasks Gauge Tasks
pgtask.worker.concurrency.configured Gauge Handlers
pgtask.worker.concurrency.effective Gauge Handlers
pgtask.worker.handlers.active Gauge Handlers
pgtask.worker.slots.available Gauge Handlers
pgtask.worker.event_loop.lag Gauge Seconds
pgtask.worker.lease.renewal.age Gauge Seconds
pgtask.worker.admission.limit Gauge Handlers

Metric attributes include queue name, task name, transition state, execution outcome, and lease-renewal result. Task identifiers, payloads, results, errors, and idempotency keys are excluded to keep cardinality bounded.

Worker-capacity gauges only use pgtask.queue.name. Configure a stable service.instance.id resource attribute for each process. Backends must retain that resource boundary when aggregating several workers for one queue.

pgtask.queue.ready.tasks counts due tasks supported by the process's registered task names and handler versions. Use it for queue autoscaling. pgtask.queue.unroutable.tasks counts due tasks with no live, non-draining worker that advertises the required capability. Alert when it remains nonzero. Take the maximum across worker instances for both gauges; every replica observes the same durable queue.

Configured concurrency is the hard process limit. Effective concurrency is the current in-memory admission limit. Available slots are max(effective - active, 0). Lowering the effective limit stops new claims and never cancels an active handler. Event-loop lag measures delay beyond the one-second runtime sampling deadline. Lease-renewal age is the oldest active lease age at the renewal sampling point, or zero when the worker has no active lease.

Do not sum concurrency, handler, or slot gauges across time. Use the latest value per service.instance.id, then sum instances when you need queue capacity. Use the maximum event-loop lag and lease-renewal age across instances for alerting.

The admission-limit gauge uses bounded pgtask.admission.decision values of proposed or applied. Reasons are database_unavailable, lease_renewal_late, event_loop_lag, recovery, or manual. The default detector is observe-only. Sustained runtime lag, an unsafe lease-renewal age, or a database failure changes WorkerControl.proposed_concurrency but does not change the effective limit. This makes thresholds measurable before enforcement is enabled.

Set WorkerConfig.overload_protection.enforce only after validating thresholds for your workload. sustained_samples filters transient event-loop lag. recovery_samples requires a stable healthy window before recovery starts. Enforcement halves the effective limit down to minimum_concurrency while overload persists. Healthy samples then add one slot until the configured limit is restored. Active handlers are never cancelled, and lease renewal remains independent of claim admission.

PostgreSQL CPU, memory, connections, locks, WAL, cache, and storage are infrastructure metrics. Collect them with the OpenTelemetry Collector Contrib PostgreSQL receiver. Kubernetes node, pod, and workload metrics belong in a Collector Deployment and DaemonSet with the Kubernetes attributes processor. Keep those collectors outside the pgtask worker so worker failure cannot hide database or cluster health.

Worker health

Set WorkerConfig.health_address to serve /livez and /readyz from the dedicated Rust supervisor thread. /livez only proves that the supervisor can respond. /readyz requires open claim admission, database connectivity, a healthy notification listener, and safe lease renewal. Dependency failure removes the worker from readiness without asking Kubernetes to restart a process whose supervisor is healthy.

Worker liveness

pgtask.workers.live counts the workers the database still considers live for a queue, and every worker reports it on its heartbeat tick.

That is deliberately not the same as counting the processes reporting metrics. A worker whose heartbeat is failing keeps exporting everything else and stops being live, which is exactly the failure worth alerting on. Compare the two and a divergence means workers are running but the database cannot see them.

pgtask.worker.heartbeats counts attempts with a pgtask.heartbeat.outcome attribute of ok, missing when the registration has gone, or error when the call failed.

Heartbeats are not spans. They are periodic and unparented, so a span for each would add one single-span trace per worker every ten seconds and still not answer how many workers are alive.