#19739·saleor

No task-execution metrics for the Celery worker

Author: jysfCreated Sep 4, 2026Updated Sep 4, 2026

Summary

Saleor instruments its HTTP surface well — request count and duration, GraphQL operation count, duration, cost, field usage and slow operations. It also instruments outbound webhooks and the circuit breaker. There is no equivalent for Celery task execution: no task counter, no task duration, no failure counter, no queue-depth or backlog signal.

The result is that a worker outage is invisible to metrics.

Reproduction

Saleor 3.23 via saleor-platform, with an OTLP metrics exporter configured (the stock backend.env sets OTEL_TRACES_EXPORTER only, so no metrics are exported by default — that is a separate saleor-platform matter, noted here only because it is a prerequisite for reproducing).

bash
docker compose stop worker
# then place an order through the GraphQL API

Observed:

  • Orders place successfully. An end-to-end API run covering catalog → cart → shipping → payment → order completes with the worker stopped.
  • Every HTTP metric stays healthy — the API is genuinely unaffected.
  • Zero confirmation emails are delivered (verified against the bundled mailpit).
  • No metric anywhere reflects the outage.

The only observable evidence is an empty inbox.

The convention already exists

This is not a request to invent metrics. OpenTelemetry's messaging semantic conventions define this surface for a consumer — messaging.process.duration, messaging.client.operation.duration, and the messaging.* attribute set. A Celery worker is a textbook messaging consumer.

Saleor implements none of it. Verified against a live 3.23 stack:

messaging_process_duration            0 series
messaging_process_messages            0 series
messaging_client_operation_duration   0 series

grep -rl "messaging" --include="*.py" /app/saleor   ->   (no matches outside tests)

The practical consequence: tooling that derives SLOs from declared workload kinds will correctly select the messaging convention for a Celery worker, and every resulting indicator binds to nothing.

Why this matters

For a commerce platform the async path carries order confirmations, invoices, webhooks and fulfilment. A silent worker means customers place orders and hear nothing — the failure is customer-visible, but only after the fact, and only to the customer.

Questions an operator cannot answer from Saleor's current metrics:

  • Are tasks being processed at all?
  • Is there a backlog, and is it growing?
  • Which task types are failing, and how often?
  • How long is a task taking relative to normal?

Suggested direction

Saleor's telemetry layer already has what is needed — meter.create_metric with Scope.SERVICE, the MeterProxy post-fork initialisation in celeryconf.py, and the unit/attribute conventions in saleor/core/telemetry/. A small set of instruments in the same style would close the gap:

Metric Type Attributes
messaging.process.duration histogram messaging.system, messaging.destination.name, error.type
messaging.process.messages counter messaging.system, messaging.destination.name
saleor.task.queue_depth up-down counter queue.name

The first two are the OTel convention names, which would make Saleor legible to conventional consumer tooling out of the box. A saleor.task.* naming would work too but loses that. Queue depth has no settled convention, so a Saleor-namespaced name seems reasonable there.

Celery's task_prerun / task_postrun / task_failure signals are the natural hook, mirroring how graphql/metrics.py wraps the request path.

Prior art

celery-exporter and similar sidecars cover some of this from outside, but they cannot see Saleor's own task semantics and sit outside the OTel pipeline the project has otherwise adopted.