TracekitTracekit

Message Latency Tracking in Microservices

Instrument message latency tracking in microservices with OpenTelemetry spans, queue-age metrics, trace context, percentiles, and practical alert rules.

Terry Osayawe9 min read
Message Latency Tracking in Microservices

Instrumenting latency tracking for transactional messages in a microservices app requires more than one timer. Measure the producer send, queue wait, consumer processing, and final settlement separately. Then connect those measurements with trace context.

This split shows whether a delayed payment, order, or notification waits at the producer, inside the broker, or in consumer code.

The short answer

Use this measurement model for each critical message flow:

  1. Create a producer span for the send or publish operation.
  2. Inject the message creation context into broker headers or attributes.
  3. Measure queue age when the consumer receives the message.
  4. Create a consumer span around the handler.
  5. End the operation after acknowledgement, commit, or settlement.
  6. Record duration histograms with low-cardinality attributes.
  7. Alert on percentiles and backlog, not average latency alone.

OpenTelemetry messaging conventions define producer, receive, process, and settle operations. They also define how spans relate across asynchronous boundaries.

Split end-to-end latency into four parts

A single end-to-end number tells you that a transaction is late. It does not show where the delay begins.

MeasurementStartsEndsWhat it usually reveals
Producer sendBefore publishBroker accepts or rejects the messageSerialization, network, broker acknowledgement, or producer throttling
Queue ageBroker timestamp or application publish timeConsumer receives the messageBacklog, partition imbalance, unavailable consumers, or scheduled delivery
Consumer processingHandler startsHandler finishesBusiness logic, database calls, downstream APIs, or retries
SettlementHandler finishesCommit, acknowledgement, or settlement completesBroker coordination, offset commits, or acknowledgement failures

Treat end-to-end latency as the full business interval. For example, an order event starts when the producer accepts the order. It ends when the consumer commits the result.

Do not combine scheduled delivery with unexpected queue delay. A message designed to wait ten minutes is not ten minutes late.

Define the transaction boundary first

Write one sentence before you add instrumentation:

The transaction starts when ___ and succeeds when ___.

This sentence prevents three common errors:

  • The producer timer stops before the broker confirms the publish.
  • The consumer timer stops before a database transaction commits.
  • A retry creates a new, unrelated trace with no link to the first attempt.

Choose a stable business operation name, such as order-confirmation or payment-receipt. Keep unique order, payment, and user identifiers out of metric labels.

Propagate trace context with every message

The producer must attach trace context to the message. The consumer must extract it before it starts processing.

W3C Trace Context standardizes traceparent and tracestate. Message brokers carry these values in Kafka headers, AMQP properties, SQS attributes, or an equivalent metadata field.

Use this broker-neutral sequence:

PRODUCER
  start SEND span
  inject message creation context into metadata
  publish message
  wait for broker result
  end SEND span

CONSUMER
  receive message
  extract message creation context
  start PROCESS span or add a span link
  run handler
  commit or acknowledge
  end PROCESS and SETTLE operations

For a single message, a consumer process span can use the message creation context as its parent. For batches, use span links. One batch span cannot have several parents.

OpenTelemetry recommends message creation context because broker transport traces cannot always connect producer work to consumer work.

Instrument the producer send

Start the producer span immediately before the client receives the message for sending. End it after the broker returns success or failure.

Use these standard attributes where they apply:

AttributeExamplePurpose
messaging.systemkafkaIdentifies the broker type
messaging.destination.nameordersIdentifies the topic or queue
messaging.operation.namesendNames the system-specific operation
messaging.operation.typesendGives analysis tools a stable operation type
messaging.destination.partition.id3Helps isolate a hot or delayed partition
error.typetimeoutRecords a failed operation category

Record the broker result on the span. A client call that returns before broker acknowledgement measures enqueue intent, not confirmed publication.

For batched sends, create a message context for each item when you need end-to-end correlation. Link the batch send span to those contexts.

Measure queue age without hiding clock problems

Queue age is the time between a trusted publish timestamp and consumer receipt. It is often the fastest way to separate broker backlog from slow handler code.

Prefer a broker-provided timestamp when the broker defines its meaning. Otherwise, add an application publish timestamp before send.

Cross-host subtraction needs synchronized clocks. Clock drift can make queue age negative or inaccurate. Monitor time synchronization, and clamp impossible values before they enter dashboards.

Do not use queue age alone. Compare it with:

  • Consumer lag or queue depth
  • Active consumer count
  • Redelivery count
  • Partition or shard
  • Producer send duration
  • Consumer process duration

This comparison separates capacity problems from broker or application problems.

Measure consumer processing and settlement

Start the process span when the application receives the message for handling. Do not start it while a client library only prefetches or caches the message.

End the process span after the business operation completes. Record exceptions and mark failed operations with error.type.

Settlement deserves separate visibility when it can block or fail. This includes Kafka offset commits, RabbitMQ acknowledgements, and similar broker operations.

A useful trace shows this order:

send orders
  └─ queue wait
      └─ process orders
          ├─ validate order
          ├─ charge payment
          └─ write receipt
      └─ settle orders

If the consumer publishes another message, start another producer operation inside the active consumer context. This preserves the transaction across the next queue.

Record duration histograms

OpenTelemetry messaging metrics define two useful duration histograms:

  • messaging.client.operation.duration measures producer or consumer client operations.
  • messaging.process.duration measures consumer processing.

Both use seconds. The conventions remain in development, so check your instrumentation library before you change existing metric names.

Add a custom queue-age histogram only when your broker integration does not provide an equivalent measurement. Give it a clear unit and document its timestamp source.

Keep metric dimensions bounded. Good dimensions include service, environment, broker system, destination template, operation type, and result.

Do not put message.id, order.id, user.id, or raw destination values into metric labels. Put message identifiers on single-message spans when you need trace-level lookup.

Use percentiles and a latency budget

An average can stay healthy while a small group of transactions becomes very slow. Use p50 for normal behavior, p95 for broad degradation, and p99 for tail latency.

Divide the business latency budget across each stage. This example uses ratios instead of invented universal thresholds:

StageBudget questionAlert input
ProducerHow much time can confirmed publish use?Send duration percentile and error rate
QueueHow long can work wait before processing?Queue-age percentile and backlog
ConsumerHow much time can handler work use?Process duration percentile and error rate
SettlementHow long can commit or acknowledgement use?Settlement duration and failure rate

Set the actual values from your transaction requirement and normal production baseline. A payment receipt and a nightly export need different budgets.

Handle retries, batches, and dead-letter queues

Asynchronous systems change message paths. Your instrumentation must show those changes.

Retries

Record each attempt as a new processing span. Link it to the original message context. Add a bounded attempt number, and record the final failure type.

Do not extend one span across a long retry delay. That design hides the difference between processing time and waiting time.

Batches

Use span links from the batch receive or process span to each message creation context. Record messaging.batch.message_count on the batch operation.

Keep per-message work visible when one slow item can block the batch. Otherwise, the batch average hides the delayed message.

Dead-letter queues

Treat dead-letter publication as another producer operation. Preserve a link to the failed processing attempt.

Record a low-cardinality reason category. Keep the full exception on the trace instead of a metric label.

Diagnose a latency alert in five steps

When an alert fires, compare the same transaction window across traces and metrics:

  1. Check the end-to-end p95 and p99 change.
  2. Compare producer, queue, consumer, and settlement durations.
  3. Group slow traces by destination, partition, consumer service, and error type.
  4. Open one slow trace and compare it with a normal trace.
  5. Verify whether a release, backlog, retry pattern, or downstream call changed.

Use this triage table:

SignalLikely starting point
Producer duration risesBroker acknowledgement, network, serialization, or producer throttling
Queue age rises while processing stays flatConsumer capacity, partition balance, pause, or backlog
Process duration risesHandler code, database, cache, or downstream service
Settlement duration risesCommit or acknowledgement path
Only one partition slowsHot key, skewed routing, or a partition-specific consumer issue
Trace breaks at the brokerMissing context injection or extraction

Apply this model with Tracekit

Tracekit accepts existing OpenTelemetry traces and metrics through OTLP over HTTP. Point your exporter at the documented /v1/traces and /v1/metrics endpoints.

If you use Go, the Go integration guide shows OpenTelemetry wrappers for Sarama and RabbitMQ. The distributed tracing guides cover architecture and context propagation patterns.

Use Tracekit traces to find the slow stage. Use metrics to confirm whether the problem affects one message or a wider transaction group.

When a slow consumer needs more code context, add a Tracekit capture point near the handler. Dynamic logs capture bounded runtime state without another deployment. Keep normal application logs in your logger.

You can also use the OpenTelemetry config generator to create an exporter configuration for your stack.

Implementation checklist

  • Define the business start and success boundary.
  • Create producer send spans.
  • Inject message creation context into metadata.
  • Measure queue age from a documented timestamp.
  • Extract context before consumer processing.
  • Create process spans and settlement visibility.
  • Use span links for batches and fan-out.
  • Record operation and processing duration histograms.
  • Keep metric labels low-cardinality.
  • Track retries and dead-letter publication.
  • Alert on p95, p99, backlog, and failures.
  • Test one normal, delayed, retried, and failed message.

Frequently asked questions

Should queue wait be a span?

Usually, producer and consumer spans provide the trace boundary. Queue age can be a derived measurement or metric. Do not invent a fake active process inside the broker unless you instrument the broker itself.

Use a parent for a simple single-message flow when your instrumentation supports it. Use links for batches, fan-out, and processing with another active context.

Which latency should page the on-call engineer?

Page on a user-impacting transaction objective. Include queue age, processing duration, failures, and backlog in the alert context. Do not page on a small internal change with no business impact.

Can metrics replace traces for message latency?

No. Metrics show the size and spread of the problem. Traces show the delayed path and the operation that used the time. Use both signals together.

Sources

Share this post

Related Posts

Migrate to OpenTelemetry Without Downtime
8 min

Migrate to OpenTelemetry Without Downtime

Migrate to OpenTelemetry without downtime with a parallel Collector path, trace parity checks, and a safe service-by-service cutover.

opentelemetryapm-migration