How to Fix High Latency in Microservices Communication

Engineers analyzing latency between microservices in a distributed system

High latency in microservices communication is usually a critical-path problem, not simply a slow-service problem. A request may wait for a worker, DNS lookup, connection, proxy, database, message broker, external provider, or several downstream calls. The service receiving the request may contribute very little of the total delay.

The safest approach is to classify where time is spent before changing protocols, adding replicas, increasing connection pools, or enabling more retries. Those changes can move the bottleneck or amplify load without improving user-visible latency.

A trace-first diagnosis flow

Use the following flow for one affected operation, such as a specific API route or RPC method:

Client request
  → entry queue
  → DNS resolution
  → connection and TLS setup
  → gateway or proxy
  → service queue
  → local processing
  → downstream calls
  → response serialization and transfer
  1. Define the symptom precisely. Record the operation, status outcome, region, application version, request volume, payload range, and p50, p95, and p99 latency. An average alone can hide a severely degraded tail.
  2. Inspect a complete distributed trace. Identify the longest span on the critical path and any time not represented by a child span.
  3. Separate waiting from execution. Measure request queues, worker queues, connection-pool acquisition, broker delay, and job start delay where possible.
  4. Match the evidence to a failure mode. Do not assume that moderate CPU means the service has spare capacity; it may be waiting on a pool, lock, network operation, or dependency.
  5. Apply the narrowest remediation. Remove an unnecessary call before replacing the communication protocol. Fix connection reuse before increasing the pool. Restrict retries before scaling an overloaded dependency.
  6. Verify the entire operation again. A local span becoming faster is not sufficient if end-to-end tail latency, errors, or downstream load become worse.

Failure-mode map

Observed signature Likely direction Evidence to collect First remediation to evaluate
p50 is stable while p99 rises Queueing, a slow endpoint, garbage collection, retries, or fan-out outliers Slow traces, queue duration, retry count, latency by pod and zone Isolate the slow branch and bound its concurrency
Delay occurs before the connection begins DNS resolution or resolver configuration DNS timing, CoreDNS health, search domains, resolution failures Correct discovery problems and reduce needless lookups through connection reuse
Connect or TLS time is repeatedly high Short-lived clients, connection churn, gateway behavior, or network problems Connection creation rate, requests per connection, handshake time, socket failures Reuse supported clients, channels, and pools
Service processing is short but total time is long Connection-pool, worker, thread, stream, or dependency wait Pool acquisition duration, pending requests, active workers, child spans Address the constrained resource rather than optimizing local code
Traffic rises after a dependency slows Retry amplification Original requests, retry attempts, backoff, retry outcomes, total deadline Restrict retry conditions and introduce a retry budget
One pod or zone is consistently slower Endpoint imbalance, resource pressure, version differences, or unhealthy processes Latency, errors, traffic, image version, node and zone by endpoint Correct routing or health detection without ejecting excessive capacity
Latency grows with cart size or item count Sequential lookups, unbounded fan-out, large payloads, or repeated serialization Downstream calls per request, payload bytes, branch concurrency Batch, deduplicate, limit concurrency, or return fewer fields
Only cross-region requests are slow Network distance, routing, or remote data placement Client, gateway, service, cache, and database locations Keep related services and data closer where correctness permits

These signatures narrow the investigation; they do not prove a cause. Confirm the suspected failure mode with traces, metrics, and controlled logs.

Trace the complete critical path

A useful trace includes the entry service, downstream HTTP or RPC calls, database and cache operations, and relevant message publication. Propagate trace context through HTTP headers, gRPC metadata, message attributes, queue consumers, gateways, and service meshes.

Record errors, cancellations, deadlines, retries, service version, region, zone, and instance attributes. Avoid unbounded metric labels such as request IDs, user IDs, raw URLs, or full SQL text. Individual-request identifiers belong in controlled traces or logs rather than latency metric labels.

Queue time deserves explicit instrumentation. A handler may execute quickly after waiting much longer for a worker or connection. Useful intervals include:

  • Request arrival to handler start.
  • Connection request to connection acquisition.
  • Thread-pool submission to execution.
  • Message publication to consumer processing.
  • Job creation to job start.

When calls run in parallel, their span durations overlap. Do not add every child duration and treat the sum as wall-clock latency; identify the branch that determines the critical path.

Remediation branches

Queue or pool saturation

Rising queue duration with stable throughput indicates that work is waiting even if CPU is not fully utilized. Inspect active requests, pending acquisitions, worker utilization, event-loop delay, CPU throttling, garbage collection, socket use, and downstream concurrency.

Bounded worker pools, semaphores, request queues, or proxy circuit breakers can prevent unlimited work from consuming memory and connections. A full boundary should reject or defer work predictably rather than allowing every request to expire after a long wait.

Do not increase a pool without calculating combined capacity across replicas. For example:

40 application pods
× 30 connections per pod
= up to 1,200 client connections

A larger local pool may reduce acquisition time while overwhelming the database or upstream service. Include autoscaling and failover capacity when reviewing the combined limit.

DNS, connection, or TLS delay

Creating a new client or channel for every request can repeat DNS resolution, socket establishment, TLS negotiation, authentication, and protocol setup. Reuse supported clients and pools, then monitor connection creation rate, failures, lifetime, pending acquisitions, and requests per connection.

For Kubernetes, run checks from an authorized diagnostic pod on the same workload network:

kubectl exec -it DIAGNOSTIC_POD -n NAMESPACE -- \
  nslookup SERVICE_NAME.NAMESPACE.svc.cluster.local

kubectl exec -it DIAGNOSTIC_POD -n NAMESPACE -- \
  cat /etc/resolv.conf

kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl get service -n kube-system kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns

Check for CoreDNS resource pressure, incorrect service names, missing DNS endpoints, blocked DNS traffic, and unexpected search-domain expansion. Do not replace service discovery with hard-coded pod addresses; pod addresses can change and this bypasses the intended Service behavior.

Sequential calls and excessive fan-out

Four independent calls executed sequentially place approximately all four durations on the critical path. Running them in parallel can shorten that path toward the slowest branch, but it also increases downstream concurrency and the chance that one branch is slow.

Parallelize only when operations are independent, downstream capacity can support the concurrency, cancellation is available, and partial-failure behavior is defined. Better options may be to remove duplicate calls, maintain a read model, query only required partitions, or batch identifiers:

POST /product-lookups

{
  "productIds": ["prod_1", "prod_2", "prod_3"]
}

Limit batch size to avoid unbounded requests and database queries. Adding a batch resource also changes an API contract, so review it using the same compatibility principles described in designing REST APIs for backward compatibility.

Move email, analytics, indexing, thumbnail generation, and similar noncritical work off the synchronous path when the client does not require the result. The asynchronous path still needs durable publication, idempotent consumers, retry limits, queue-age monitoring, schema compatibility, and reconciliation.

Deadlines, retries, and dependency overload

Define an end-to-end deadline and propagate the remaining time downstream. Giving every dependency the complete client timeout allows nested calls to continue after the result is no longer useful.

The following is an illustrative, vendor-neutral policy rather than deployable configuration:

operation_deadline: 1500ms

catalog_lookup:
  per_attempt_timeout: 500ms
  max_attempts: 2
  retry_on:
    - transient_unavailable
  backoff:
    initial: 50ms
    multiplier: 2
    maximum: 200ms
    jitter: enabled

rules:
  - skip_retry_if_deadline_cannot_accommodate_it
  - cancel_unneeded_downstream_work
  - limit_total_retry_traffic

Retry only failures likely to be temporary and only when the operation is idempotent or protected by an idempotency mechanism. Do not retry validation or authorization failures. Immediate, unlimited retries can multiply traffic against a dependency that is already degraded.

A timeout on a payment, order, or other state-changing request does not prove that the server performed no work. Use idempotency keys, operation-status resources, and reconciliation instead of blindly repeating an uncertain operation. External dependencies may also enforce their own quotas; investigate those separately when third-party rate limiting becomes the bottleneck.

Slow endpoints, placement, and payloads

Compare latency and errors by pod, node, zone, application version, and destination. Active health checks can detect explicit failures, while passive outlier detection can identify endpoints that repeatedly time out or reset connections. Ejection limits matter because removing too many endpoints can overload those that remain.

Map the complete geographic path when latency varies by region. Keep a service close to the database it uses most where practical, and review consistency and data-residency implications before duplicating data. For read-heavy global systems, the placement issues overlap with those in optimizing database read replicas for global applications.

For payload-heavy endpoints, measure request bytes, response bytes, serialization, parsing, compression, and allocation. Pagination, field selection, smaller summaries, streaming, or dedicated export endpoints may reduce work. Compression trades network transfer for CPU, so evaluate it with representative payloads and concurrency. Switching to gRPC, HTTP/2, GraphQL, or another protocol does not remove poor fan-out, missing deadlines, connection churn, or slow dependencies.

Illustrative diagnostic example

Consider an illustrative checkout operation with a p95 of 2.8 seconds. This is a reasoning example, not a measured Linqdev result.

Trace evidence Diagnosis Targeted change
Seven product lookups account for 840 ms One sequential request is made per cart item Introduce a bounded batch lookup
A tax-provider span takes 620 ms and frequently creates a connection Connection setup contributes to dependency time Reuse the supported provider client and connection pool
Payment takes 780 ms and includes an automatic retry The retry consumes critical-path budget and may repeat uncertain work Set a dedicated deadline and require idempotency protection
Analytics publication blocks for 190 ms The client does not need analytics completion Publish through a durable asynchronous path

The changes should be evaluated separately enough to identify which hypothesis was correct. No post-change latency number should be assumed in advance.

Verification sequence

  1. Capture a comparable baseline. Retain request volume, status outcomes, p50, p95, p99, dependency calls per operation, queue time, retry attempts, payload size, and connection creation rate.
  2. Exercise representative traffic. Include realistic payload distributions, concurrency, geographic paths, and cache states rather than one development request.
  3. Test degraded dependencies. Confirm that deadlines expire as intended, cancellations stop unnecessary work, retry traffic remains bounded, and circuit breakers prevent unlimited pending requests.
  4. Check correctness. Verify state-changing operations, idempotency behavior, cache freshness, authorization boundaries, asynchronous queue age, and reconciliation.
  5. Check displacement of load. A faster caller is not an improvement if database connections, downstream errors, memory use, or infrastructure cost rise beyond acceptable limits.
  6. Compare by version and endpoint. Look for regressions isolated to a pod, node, zone, or application version.
  7. Keep the change reversible. Monitor the same end-to-end signals during rollout and define rollback conditions. For deployment mechanics, see automating rollbacks for failed deployments.

The latency problem is resolved only when the complete operation improves without weakening correctness, resilience, security, or downstream stability.

Official references