← Back to Insights

Production investigation

Partial Failures in Distributed Systems

Written by:Better SoftwareBetter Software TeamUpdated 6 min read
Partial Failures in Distributed Systems

Quick Answer

Distributed system failures usually appear as partial degradation or rising latency rather than complete outages. Investigation correlates metrics, logs and traces along the critical request path, checks recent deployments, isolates resource contention, bypasses suspected components and tests one hypothesis at a time. Strict dependency timeouts, circuit breakers, dependency load tests and fast rollbacks reduce recurrence. Retries without backoff and restarts without isolating the cause usually make things worse.

Investigating failures in distributed systems requires comprehensive visibility across multiple services. Failures often manifest as partial degradation or increased latency, not complete outages. Pinpointing the root cause involves correlating metrics, logs, and traces from various system components. Focus on critical request paths and their deviations from expected behavior. System state changes can propagate unexpectedly, so analyze temporal relationships between events.

Why This Happens

Distributed systems inherently introduce complexity. Components rely on network communication, which is unreliable. Asynchronous operations mean states diverge across services before convergence. Individual service failures can cascade, affecting upstream and downstream dependencies. Latency spikes in one service can exhaust connection pools in another. Retries can amplify load on an already struggling dependency. Frequent deployments also introduce new codepaths into this environment. Failure modes are often non-obvious and emergent properties of the system's interactions.

Investigation Process

  1. Identify the scope. Determine affected services and users. Use dashboards to confirm recent changes or anomalous metrics. Check for recent deployments or configuration updates.
  2. Examine critical request paths. Trace a problematic request from entry point to failure. Identify components involved in this specific flow.
  3. Collect high-level metrics. Review dashboards for latency, error rates, and resource utilization (CPU, memory, network, disk I/O) across all services on the critical path. Look for synchronized deviations.
  4. Dig into service logs. Filter logs for errors and unusual patterns within affected services. Correlate timestamps across logs to observe event order. Use request IDs for correlating traces.
  5. Analyze distributed traces. Utilize tracing tools to visualize request flow across services. Identify the exact service and span where latency increased or an error originated. Look for unexpected calls or missing spans.
  6. Bypass suspected components. If possible, temporarily route around or disable a suspected component. Observe if the problem resolves or shifts. This helps isolate the fault domain.
  7. Isolate resource contention. Check for shared resource saturation. Examples include database connection pools, message queue throughput, or external API rate limits.
  8. Review recent code changes. Consult version control for recent commits to affected services. Look for changes affecting data structures, network calls, or concurrency.
  9. Hypothesize and test. Formulate a specific hypothesis about the root cause. Devise a small, targeted test to validate or invalidate it. This could involve feature flags or rollback.

Practical Example

A global e-commerce platform received reports of intermittent checkout failures. Customers saw a generic 'payment failed' message. During Q4, transaction volume increased substantially. Initial checks showed payment service metrics were healthy. Database connections were not saturated. However, an increase in error rates appeared shortly after a new promotional coupon service deployment.

Investigation began by reviewing checkout service logs. Logs showed increasing 'timeout' errors when calling the coupon service. Distributed traces revealed that calls to the new coupon service consistently took longer than the timeout configured for this dependency in the checkout service. The coupon service itself showed no resource contention. Its internal logic involved calling a legacy eligibility service. This legacy service was under-provisioned and unable to handle Q4 traffic from the new coupon service. The checkout service was timing out before the coupon service could return any response. This cascaded into payment failures. The team temporarily disabled the new coupon service via a feature flag. This restored full checkout functionality. They then scaled the legacy eligibility service and reintroduced the coupon service.

Preventing Recurrence

Preventing recurrence involves architectural, testing, and operational improvements.

  • Dependency timeouts. Implement strict, service-level timeouts for all external calls. Configure these to fail fast.
  • Circuit breakers. Deploy circuit breaker patterns around external dependencies. This prevents cascading failures and allows services to recover.
  • Load testing dependencies. Include dependency load tests in pre-production environments. Simulate high traffic scenarios against critical path components.
  • Enhanced observability. Improve distributed tracing granularity. Ensure all critical paths have full visibility into component calls. Instrument legacy services for better insights.
  • Service boundary contracts. Define clear API contracts and performance expectations between services. Enforce these through automated testing.
  • Chaos engineering. Regularly inject failures into non-production environments. Understand how the system reacts to component degradation.
  • Rollback strategies. Develop fast, reliable rollback mechanisms for all service deployments.

What Teams Usually Do Instead

Many teams immediately focus on the service reporting the error. They restart services without isolating the root cause. This often provides temporary relief but does not solve the underlying issue. Teams might also add more compute resources to the 'failing' service. This often masks the actual problem of an upstream or downstream dependency. Without systemic tracing, engineers waste time examining irrelevant logs. They operate on assumptions about service interactions rather than empirical data. This leads to recurring incidents. They also sometimes implement complex retry logic without backoff. This can exacerbate problems by increasing load on already struggling services.

Key Takeaways

  • Distributed failures are often symptom, not cause.
  • Trace critical paths end-to-end for visibility.
  • Correlate metrics, logs, and traces temporally.
  • Isolate faulty components using hypotheses and tests.
  • Implement timeouts and circuit breakers for resilience.

See also how we help teams build products and a production debugging workflow for distributed systems.

Frequently Asked Questions

Why Are Distributed System Failures Hard to Diagnose?

We find they come from unreliable network communication, asynchronous states that diverge before converging, cascading failures, latency spikes that exhaust connection pools elsewhere, and retries that amplify load on a struggling dependency. The failure modes are often non-obvious and emerge from how services interact.

How Do You Investigate a Distributed System Failure?

We identify the scope, trace a problematic request along its critical path, and review metrics, logs and distributed traces across every service on it. We check recent deployments and code changes, look for shared resource saturation, bypass suspected components, then hypothesize and test with feature flags or rollback.

What Do Distributed Traces Show?

We use them to visualize request flow across services and find the exact service and span where latency increased or an error began. They also reveal unexpected calls or missing spans. In the checkout example, traces showed calls to a new coupon service running slower than the timeout configured in the checkout service.

How Do You Prevent Cascading Failures?

We set strict service-level timeouts that fail fast, deploy circuit breakers around external dependencies, load test dependencies before production, enforce API contracts between services, practice chaos engineering in non-production, and keep fast rollbacks for every deployment. Together they stop one degraded service from taking others down.

What Do Teams Usually Do Instead?

We often see teams focus on the service reporting the error, restart services without isolating the root cause, add compute to the failing service, and write retry logic without backoff. That gives temporary relief, masks upstream or downstream dependency problems and can add load to services that are already struggling.

About the author

Better Software

Better Software Team

Product and engineering team

Better Software Team is the product and engineering team at Better Software. We build new software products and improve existing applications and systems, with deeper experience in healthcare, finance and energy.