← Back to blog
Solution 4 min read 炬鲸团队

Slow Microservices Triage? A Trace-Centric Troubleshooting Solution

In microservices, a single request spans a dozen services with scattered logs and siloed metrics. This post lays out a trace-centric triage approach — sampling strategy, error fingerprinting, and alert correlation — to cut MTTR from hours to minutes.

The Symptom: You Know It's Slow, Not Where

A classic microservices headache: a user reports "checkout hangs," you see gateway P99 spiking, but which of the dozen services behind it is slow comes down to manual log digging. Logs are scattered, metrics are siloed, and only the trace ties one request together end to end.

OBSERVE's approach is trace-centric: use the trace to find which hop is slow, use that hop's logs to see why, and use metrics to measure the blast radius. All three hang off the same traceId.

Step 1: Sampling Strategy Determines Triage Quality

Full sampling is expensive, but fixed 1% sampling misses rare failures. We recommend combining head and tail sampling:

  • Head-based: sample 10% at the entry point to keep a steady stream of analyzable traffic.
  • Tail-based: mark at entry without sampling, and decide at exit — keep the full trace if any span has status = error or elapsed > threshold.

This keeps 100% of error traces and low-cost sampling for healthy traffic. In config, collectors send trace data to the OTLP gateway, which makes the tail decision:

sampling:
  head:
    probability: 0.10
  tail:
    force_keep:
      - condition: "span.status == 'ERROR'"
      - condition: "span.duration_ms > 1500"

Step 2: From Slow Trace to the Right Logs

Once you've found the slow span, the next move is to see its logs:

  1. Drill into logs from the span — the system looks up every log line within that span's lifetime using traceId + spanId, no manual condition typing.
  2. Read the duration breakdown — a span's time splits into child-span calls, DB queries, serialization, and so on. Unfold the breakdown to see whether it's network retries or a slow SQL statement.
  3. Check the error fingerprint — similar errors (same exception type plus same top stack frame) aggregate into one fingerprint with "first seen" and "count in the last hour," telling you whether this is new or pre-existing.

Step 3: Correlate Metrics and Alerts

A trace answers "how did this request behave"; an alert answers "how wide is the impact." Export trace metrics (latency percentiles, error rate) per service into the metric system, and let alert rules consume them directly:

alert: service_latency_p95_high
expr: trace_p95_ms{service="order"} > 1000
for: 5m
labels: {severity: warning}
annotations:
  summary: "order service P95 latency above 1s"
  trace_query: "service=order AND elapsed>1000 ORDER BY elapsed DESC"

The alert detail embeds a trace query link, so on-call opens straight to the slow-trace top list — no assembly from alert to evidence.

Adoption Tips

  • Don't instrument everything at once. Start with core flows (checkout, payment, login), close the "slow → logs → root cause" loop, then expand.
  • Standardize traceId propagation with OpenTelemetry SDK auto-instrumentation plus gateway-side injection, so mixed-language services don't break the trace.
  • Prune error fingerprints regularly. Fingerprints go stale across releases; keep them 30 days or alerts drown in historical noise.
  • Make sampling tunable. Raise head sampling during promotions and lower it after; don't hard-code it.

With the trace at the center and logs and metrics attached to it, triage stops meaning "search logs across dozens of services" and becomes "open one slow trace, drill into its logs once." The MTTR reduction is real.