A fundamental misunderstanding about distributed systems is that they are inherently more reliable than single machines.

Let’s watch a typical request flow through a simple, but not trivial, distributed system. Imagine a user requesting a product page on an e-commerce site.

User -> Load Balancer (LB) -> Web Server (WS1) -> Product Service (PS1) -> Database (DB1)

The Load Balancer, a Nginx instance, receives the request and, using a round-robin algorithm, forwards it to Web Server WS1. WS1, a Node.js application, receives the request, parses it, and then makes an RPC call to the Product Service, PS1. PS1, a Go application, receives the RPC, queries DB1 (a PostgreSQL instance) for product details, formats the response, and sends it back to WS1. WS1 then renders the HTML and sends it back to the user.

This looks straightforward, but the complexity arises from the potential failure points and the cascading effects.

Consider a scenario where the Product Service (PS1) is struggling to keep up with requests.

Problem: PS1 is intermittently failing to respond to RPC calls from WS1.

What actually broke at the system level: The Product Service component is exhibiting high latency and is timing out when responding to requests from the Web Server, causing the Web Server to fail to serve user requests.

Here are the most common reasons this happens, and how to fix them:

  1. Database Connection Pool Exhaustion: The Product Service might have a limited number of connections to DB1, and all are in use. New requests from WS1 cannot acquire a connection to query the database, leading to timeouts.

    • Diagnosis: On PS1, run netstat -anp | grep <db_port> | wc -l to see the number of active connections to the database. Check DB1’s pg_stat_activity view for idle connections.
    • Fix: Increase the max_connections parameter in PS1’s configuration (e.g., NODE_ENV=production MAX_DB_CONNECTIONS=50 node app.js). The specific parameter name will vary by language/framework (e.g., HikariCP.maximumPoolSize in Java).
    • Why it works: This allows PS1 to establish and maintain more concurrent connections to DB1, reducing the likelihood of connection waits.
  2. Under-provisioned Product Service Resources: PS1 itself might not have enough CPU or memory to process incoming requests efficiently, especially during traffic spikes. This leads to slow processing and eventual timeouts.

    • Diagnosis: On the PS1 host, run top or htop to observe CPU and memory utilization. Look for sustained high CPU (e.g., >90%) or low free memory.
    • Fix: Scale up the instance size of PS1 (e.g., change from a t3.medium to a t3.xlarge on AWS) or add more instances and update the load balancer configuration for PS1.
    • Why it works: More powerful hardware or more instances allow PS1 to handle a greater volume of requests concurrently without becoming a bottleneck.
  3. Network Latency or Packet Loss: The network path between WS1 and PS1 could be experiencing issues, causing RPC requests to take too long or get dropped, leading to timeouts on WS1.

    • Diagnosis: From WS1, run ping <ps1_ip> and mtr <ps1_ip> to check for high latency and packet loss.
    • Fix: Investigate network infrastructure between the two services. This might involve checking switch configurations, firewall rules, or consulting with network engineers. In cloud environments, ensure services are in the same availability zone or region if low latency is critical.
    • Why it works: Reducing or eliminating packet loss and high latency ensures that requests and responses between WS1 and PS1 arrive promptly and reliably.
  4. Inefficient Database Queries: A slow-running query within PS1 can tie up database connections and PS1’s processing threads for an extended period.

    • Diagnosis: On DB1, run SELECT * FROM pg_stat_activity WHERE state = 'active'; to find long-running queries. Use EXPLAIN ANALYZE <your_query> to identify bottlenecks in specific queries.
    • Fix: Add appropriate indexes to the database tables being queried by PS1. For example, CREATE INDEX idx_product_id ON products (id); if PS1 frequently queries products by id. Optimize the query itself if it’s complex.
    • Why it works: Indexes allow the database to locate data much faster, reducing query execution time and freeing up resources.
  5. Application Code Bugs in Product Service: A recent deployment might have introduced a bug causing infinite loops, excessive garbage collection pauses, or unhandled exceptions that prevent PS1 from responding.

    • Diagnosis: Check PS1’s logs for unhandled exceptions, stack traces, or recurring error messages. Monitor application-level metrics like error rates and garbage collection activity.
    • Fix: Roll back to a previous stable version of PS1 or deploy a hotfix addressing the identified bug.
    • Why it works: Correcting the bug resolves the underlying issue preventing PS1 from functioning correctly.
  6. External Dependency Timeout: PS1 might be calling another service (e.g., an inventory service) that is slow or unresponsive, and PS1’s timeout for that dependency is too short, causing PS1 to fail its own RPC call from WS1.

    • Diagnosis: Examine PS1’s logs for timeouts related to calls to other services. Check the health and performance of those downstream services.
    • Fix: Increase the timeout value in PS1’s configuration for its dependency (e.g., INV_SERVICE_TIMEOUT_MS=5000 in PS1’s environment variables). If the dependency is truly problematic, address its issues first.
    • Why it works: Giving the downstream dependency more time to respond prevents PS1 from prematurely failing due to a temporary slowdown in another part of the system.

After fixing these, the next error you’ll likely encounter is a 502 Bad Gateway if the Load Balancer is misconfigured to point to a non-existent or unhealthy Web Server instance.

Want structured learning?

Take the full Distributed Systems course →