Marcio Cunha

Slow microservices? How to debug bottlenecks with OpenTelemetry

Learn how to implement distributed tracing using OpenTelemetry to eliminate hidden latency and performance bottlenecks in your microservices architecture.

Marcio Cunha•3 min
Also available in:PortuguêsEspañol
Summary
  • Distributed tracing connects logs from multiple services into a single cohesive timeline.
  • Spans and traces form the backbone for measuring exact time spent on network operations.
  • Automatic instrumentation accelerates OpenTelemetry adoption in existing codebases.
  • Dependency mapping reveals hidden bottlenecks that local tests would never uncover.
  • Continuous latency analysis prevents cascading slowness in distributed systems.

The challenge of finding slowness in microservices

When we split a monolithic system into independent blocks, we gain deployment agility but lose direct visibility into request flows. Distributed tracing solves this pain by carrying a unique identifier in every request traveling between services. In practice, this means we can follow a user click from the entry gateway down to the deepest database table.

In modern architectures, the biggest challenge isn't making the system work, but understanding why a simple screen takes seconds to load. Legacy monitoring tools only show that server A or B is overloaded, failing to reveal the root cause. This is where OpenTelemetry steps in, providing a consolidated industry standard to collect metrics, logs, and traces in a unified way.

What Traces and Spans mean in practice

To understand OpenTelemetry, we need to translate its core concepts into daily software engineering routines. A Trace represents the complete end-to-end journey of a transaction, while a Span is an individual unit of work within that journey, such as a SQL query or an HTTP call. To further strengthen your resilience against structural failures, review the strategies outlined in how to protect microservices from cascading failures with circuit breakers.

Each span carries crucial metadata called attributes, storing information such as input parameters, HTTP status codes, and user identifiers. When one service calls another over the network, the trace context is injected into HTTP request headers. The receiving service extracts this context and continues the chain, ensuring the timeline remains intact and accurate.

Implementing telemetry in code

Below is a practical example in Node.js using the OpenTelemetry SDK to initialize tracing and record custom spans during a critical business operation. Before applying these practices, ensuring data integrity and avoiding logic hallucinations in complex integrations is essential, topics covered in why AI models hallucinate and how to harden your prompts.

const { NodeTracerProvider } = require('@opentelemetry/sdk-node');
const { trace } = require('@opentelemetry/api');

const provider = new NodeTracerProvider();
provider.register();

const tracer = trace.getTracer('payment-service');

async function processPayment(data) {
  const span = tracer.startSpan('process_payment_db');
  try {
    // Simulates expensive database operation
    await new Promise((resolve) => setTimeout(resolve, 150));
    span.setStatus({ code: 1 }); // Ok
  } catch (error) {
    span.recordException(error);
    span.setStatus({ code: 2, message: error.message });
  } finally {
    span.end();
  }

This code demonstrates how to create a manual span by wrapping a latency-sensitive operation. If a network failure or database slowdown occurs, the exception is recorded directly within the trace context, easing subsequent audits in visualization tools like Jaeger or Zipkin.

Identifying hidden bottlenecks with OpenTelemetry

When telemetry is active across the microservices mesh, visualization dashboards stop showing abstract numbers and start displaying the application's critical path. We often discover that 80% of an API's slowness does not come from business logic itself, but from unnecessary sequential calls to external services or queries without proper indexing.

Another common issue is the convoy effect, where multiple services wait for a slow dependency response, locking connection pools and exhausting machine resources. Measuring wait times in queues and parallel threads with dedicated spans helps isolate exactly which component is breaking the system's performance promise.

Best practices to avoid noise and overhead

Collecting 100% of all requests in production environments with high traffic volume can inflate storage costs and impact application performance. The recommended strategy is to use intelligent sampling, where only a representative fraction of traces is sent to the backend, ensuring errors and slow requests are always captured.

Additionally, standardizing span naming and avoiding the storage of sensitive or personal data (PII) in OpenTelemetry attributes is a security and compliance obligation. Consistent operation names make it easier to build automated alerts and executive dashboards that genuinely reflect software ecosystem health.

Conclusion and next steps in observability

Adopting OpenTelemetry transforms how engineering teams handle performance issues in distributed environments, replacing guesswork with precise mathematical evidence. By mapping every span and understanding the complete request journey, previously invisible bottlenecks become easy to diagnose and fix.

The initial investment in code instrumentation yields immediate returns in product stability and end-user satisfaction. Start by instrumenting your system's most critical flows and gradually expand across your entire microservices architecture.