<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ABHIRAMI RAJEEV</title>
    <description>The latest articles on DEV Community by ABHIRAMI RAJEEV (@abhirami_rajeev_d5bf9d241).</description>
    <link>https://dev.to/abhirami_rajeev_d5bf9d241</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4075002%2F1c445681-9773-4816-86a5-cda0af94db1b.png</url>
      <title>DEV Community: ABHIRAMI RAJEEV</title>
      <link>https://dev.to/abhirami_rajeev_d5bf9d241</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhirami_rajeev_d5bf9d241"/>
    <language>en</language>
    <item>
      <title>What Happens When a Backend Service Makes 10,000 Concurrent Network Requests?</title>
      <dc:creator>ABHIRAMI RAJEEV</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:14:50 +0000</pubDate>
      <link>https://dev.to/abhirami_rajeev_d5bf9d241/what-happens-when-a-backend-service-makes-10000-concurrent-network-requests-4em5</link>
      <guid>https://dev.to/abhirami_rajeev_d5bf9d241/what-happens-when-a-backend-service-makes-10000-concurrent-network-requests-4em5</guid>
      <description>&lt;p&gt;When we talk about Go, one of the first things we hear is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Go can handle thousands of concurrent operations."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's true—but it can also create a dangerous misconception.&lt;/p&gt;

&lt;p&gt;If a backend service needs to make 10,000 network requests concurrently, is creating 10,000 goroutines enough?&lt;/p&gt;

&lt;p&gt;Not really.&lt;/p&gt;

&lt;p&gt;The interesting part starts when we look underneath the goroutine.&lt;/p&gt;

&lt;p&gt;A network request involves much more than application-level concurrency. We also have sockets, file descriptors, TCP connections, ephemeral ports, connection pools, DNS, timeouts, operating-system limits, and the capacity of the downstream service.&lt;/p&gt;

&lt;p&gt;So what actually happens when a Go service tries to make 10,000 network requests at the same time?&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Start with the obvious approach
&lt;/h2&gt;

&lt;p&gt;Imagine a service that needs to call another API 10,000 times.&lt;/p&gt;

&lt;p&gt;A simple Go implementation might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c"&gt;// Process response&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this looks completely reasonable.&lt;/p&gt;

&lt;p&gt;Go can create thousands of goroutines, and goroutines are lightweight compared with operating-system threads.&lt;/p&gt;

&lt;p&gt;But there is an important distinction:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10,000 goroutines do not automatically mean 10,000 network connections.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And understanding that distinction is where things get interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Goroutines are not network connections
&lt;/h2&gt;

&lt;p&gt;A goroutine is a unit of execution managed by the Go runtime.&lt;/p&gt;

&lt;p&gt;A network connection is an operating-system resource represented by a socket.&lt;/p&gt;

&lt;p&gt;They are related, but they are not the same thing.&lt;/p&gt;

&lt;p&gt;You could have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 goroutines
        |
        +---- 100 TCP connections
        |
        +---- 500 TCP connections
        |
        +---- 10,000 TCP connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual number depends on how the application uses the HTTP client and how connections are reused.&lt;/p&gt;

&lt;p&gt;For example, HTTP keep-alive allows multiple requests to reuse an existing TCP connection.&lt;/p&gt;

&lt;p&gt;HTTP/2 goes even further: multiple concurrent requests can be multiplexed over the same TCP connection.&lt;/p&gt;

&lt;p&gt;So the first question should not be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How many goroutines do I have?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It should be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"How many network connections and other resources does this workload actually require?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. What happens underneath an HTTP request?
&lt;/h2&gt;

&lt;p&gt;A simplified request looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     |
     v
HTTP Client
     |
     v
DNS Resolution
     |
     v
Socket
     |
     v
TCP Connection
     |
     v
TLS (for HTTPS)
     |
     v
HTTP Request
     |
     v
Downstream Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a new TCP connection is required, the operating system needs to allocate resources for it.&lt;/p&gt;

&lt;p&gt;That means our 10,000 concurrent requests can start interacting with limits outside our Go code.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. File descriptors become relevant
&lt;/h2&gt;

&lt;p&gt;On Unix-like systems, sockets are represented by file descriptors.&lt;/p&gt;

&lt;p&gt;A process has a limit on how many file descriptors it can have open.&lt;/p&gt;

&lt;p&gt;You can inspect the limit on many Linux systems with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ulimit&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose your process is allowed only a few thousand open file descriptors.&lt;/p&gt;

&lt;p&gt;If your application tries to establish thousands of simultaneous connections, you can hit that limit.&lt;/p&gt;

&lt;p&gt;At that point, the problem isn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Go cannot create enough goroutines."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"the process cannot obtain enough OS resources."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an important distinction when debugging high-concurrency services.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. What about ephemeral ports?
&lt;/h2&gt;

&lt;p&gt;This is another interesting limit.&lt;/p&gt;

&lt;p&gt;When your machine initiates an outbound TCP connection, it needs a source port.&lt;/p&gt;

&lt;p&gt;Conceptually, a connection looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source IP : Source Port
        |
        | TCP
        |
Destination IP : Destination Port
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source port is generally selected from the system's ephemeral-port range.&lt;/p&gt;

&lt;p&gt;So if a service creates a very large number of outbound connections, available ephemeral ports can become a constraint.&lt;/p&gt;

&lt;p&gt;But there is an important nuance:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You cannot simply say "10,000 requests = 10,000 ports."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Connections can be reused, and the relevant TCP connection identity includes the source/destination addresses and ports.&lt;/p&gt;

&lt;p&gt;HTTP connection reuse therefore becomes extremely important.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Connection pooling changes the picture
&lt;/h2&gt;

&lt;p&gt;This is one of the reasons HTTP clients are more sophisticated than they initially appear.&lt;/p&gt;

&lt;p&gt;Instead of creating a brand-new TCP connection for every request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1 → TCP connection 1
Request 2 → TCP connection 2
Request 3 → TCP connection 3
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a client can reuse connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 +--&amp;gt; Request 1
                 |
TCP Connection --+--&amp;gt; Request 2
                 |
                 +--&amp;gt; Request 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids repeatedly paying the cost of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TCP connection establishment&lt;/li&gt;
&lt;li&gt;TLS handshakes&lt;/li&gt;
&lt;li&gt;socket creation&lt;/li&gt;
&lt;li&gt;port allocation&lt;/li&gt;
&lt;li&gt;connection teardown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go's &lt;code&gt;net/http&lt;/code&gt; client uses a &lt;code&gt;Transport&lt;/code&gt; to manage connection reuse and pooling.&lt;/p&gt;

&lt;p&gt;That means how you configure and reuse your HTTP client matters.&lt;/p&gt;

&lt;p&gt;For example, repeatedly creating clients or transports inside a hot path can prevent you from getting the connection reuse you actually want.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. HTTP/2 changes the model again
&lt;/h2&gt;

&lt;p&gt;With HTTP/1.1, multiple requests can reuse connections, but concurrency across requests is still constrained by the connection model and client/server behavior.&lt;/p&gt;

&lt;p&gt;HTTP/2 introduces multiplexing.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Request 1
                    |
                 Request 2
                    |
                 Request 3
                    |
                    v
              One TCP Connection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple HTTP/2 streams can share the same connection.&lt;/p&gt;

&lt;p&gt;So you might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 concurrent requests

        ↓

far fewer TCP connections

        ↓

many HTTP/2 streams
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one reason the phrase &lt;strong&gt;"10,000 concurrent requests"&lt;/strong&gt; is not enough information to understand the actual network load.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Then comes the downstream service
&lt;/h2&gt;

&lt;p&gt;Even if our own service can technically create 10,000 concurrent requests, that doesn't mean we should.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Backend Service
                 /    |    \
                /     |     \
             10,000 concurrent requests
                       |
                       v
                Downstream API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the downstream service can comfortably process only 1,000 concurrent requests, sending 10,000 requests at once may simply overwhelm it.&lt;/p&gt;

&lt;p&gt;We can end up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;More concurrency
       ↓
More load
       ↓
Higher latency
       ↓
Timeouts
       ↓
Retries
       ↓
Even more load
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where a local concurrency decision becomes a distributed-systems problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Unlimited concurrency is not always better
&lt;/h2&gt;

&lt;p&gt;A common approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if &lt;code&gt;items&lt;/code&gt; contains 1 million elements, we've just created a potentially huge amount of concurrent work.&lt;/p&gt;

&lt;p&gt;A better approach is to introduce a limit.&lt;/p&gt;

&lt;p&gt;For example, a semaphore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;sem&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;

        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the application can have at most approximately 100 operations in the controlled section at once.&lt;/p&gt;

&lt;p&gt;The exact number should not be chosen randomly.&lt;/p&gt;

&lt;p&gt;It depends on things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;downstream capacity&lt;/li&gt;
&lt;li&gt;request latency&lt;/li&gt;
&lt;li&gt;CPU usage&lt;/li&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;li&gt;connection limits&lt;/li&gt;
&lt;li&gt;database capacity&lt;/li&gt;
&lt;li&gt;rate limits&lt;/li&gt;
&lt;li&gt;expected traffic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. Backpressure becomes important
&lt;/h2&gt;

&lt;p&gt;Suppose requests arrive faster than the downstream system can process them.&lt;/p&gt;

&lt;p&gt;Without backpressure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming work
     ↓
More goroutines
     ↓
More requests
     ↓
More memory
     ↓
More connections
     ↓
More timeouts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With controlled concurrency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming work
     ↓
Bounded queue
     ↓
Worker pool
     ↓
Controlled concurrency
     ↓
Downstream service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of allowing the system to consume unlimited resources, we deliberately control how much work is in flight.&lt;/p&gt;

&lt;p&gt;This is one of the most important principles in building reliable distributed systems:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A system should control the amount of work it allows into an overloaded component.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  11. Timeouts are just as important as concurrency
&lt;/h2&gt;

&lt;p&gt;Consider a request that normally takes 100 ms.&lt;/p&gt;

&lt;p&gt;Now imagine the downstream service becomes unhealthy and requests start taking 30 seconds.&lt;/p&gt;

&lt;p&gt;If we have 10,000 goroutines waiting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 goroutines
       |
       v
Waiting on slow network calls
       |
       v
Resources remain occupied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can quickly turn into resource exhaustion.&lt;/p&gt;

&lt;p&gt;That's why network operations should have explicit deadlines or timeouts.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Timeouts prevent a slow dependency from holding resources indefinitely.&lt;/p&gt;

&lt;p&gt;But timeouts also need careful design.&lt;/p&gt;

&lt;p&gt;A timeout that is too short creates unnecessary failures.&lt;/p&gt;

&lt;p&gt;A timeout that is too long allows resources to remain occupied for too long.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. What happens when requests fail?
&lt;/h2&gt;

&lt;p&gt;Now imagine 10,000 requests are made and the downstream service starts returning errors.&lt;/p&gt;

&lt;p&gt;The natural reaction might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request failed
      ↓
Retry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if all 10,000 requests retry immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 requests
      ↓
failure
      ↓
10,000 retries
      ↓
more load
      ↓
more failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how a small dependency failure can become a much larger distributed-system failure.&lt;/p&gt;

&lt;p&gt;Retries should therefore generally be combined with mechanisms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exponential backoff&lt;/li&gt;
&lt;li&gt;jitter&lt;/li&gt;
&lt;li&gt;retry limits&lt;/li&gt;
&lt;li&gt;deadlines&lt;/li&gt;
&lt;li&gt;idempotency&lt;/li&gt;
&lt;li&gt;circuit breakers&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Concurrency, networking, and reliability are deeply connected.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. So how many concurrent requests should we allow?
&lt;/h2&gt;

&lt;p&gt;There isn't a universal number.&lt;/p&gt;

&lt;p&gt;The correct concurrency limit depends on the system.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   Concurrency Limit
                         |
        +----------------+----------------+
        |                |                |
   Downstream        Network          Application
    capacity          limits           resources
        |                |                |
    Rate limits     Connections        CPU/Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to measure the system rather than simply choosing a large number.&lt;/p&gt;

&lt;p&gt;A good starting point is to load-test the service and observe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;throughput&lt;/li&gt;
&lt;li&gt;error rate&lt;/li&gt;
&lt;li&gt;CPU&lt;/li&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;li&gt;connection count&lt;/li&gt;
&lt;li&gt;file descriptors&lt;/li&gt;
&lt;li&gt;downstream saturation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then increase concurrency gradually until you find the point where additional concurrency stops improving throughput or begins degrading reliability.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. The bigger lesson
&lt;/h2&gt;

&lt;p&gt;When I started thinking about "10,000 concurrent requests", I initially thought the interesting question was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can Go handle 10,000 goroutines?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But that's actually the easy part.&lt;/p&gt;

&lt;p&gt;The more interesting questions are:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How many connections are required?&lt;/p&gt;

&lt;p&gt;Can the OS provide the required resources?&lt;/p&gt;

&lt;p&gt;Are connections being reused?&lt;/p&gt;

&lt;p&gt;How many file descriptors are being consumed?&lt;/p&gt;

&lt;p&gt;What happens to ephemeral ports?&lt;/p&gt;

&lt;p&gt;Can the downstream service handle the traffic?&lt;/p&gt;

&lt;p&gt;What happens when latency increases?&lt;/p&gt;

&lt;p&gt;What happens when requests start timing out?&lt;/p&gt;

&lt;p&gt;What happens when retries begin?&lt;/p&gt;

&lt;p&gt;Where should backpressure be applied?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why backend concurrency is not simply about creating more goroutines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency is a system-design decision.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Go runtime is only one part of the system.&lt;/p&gt;

&lt;p&gt;The operating system, network stack, HTTP client, downstream services, databases, and failure-handling mechanisms all participate in determining how much concurrency the system can safely handle.&lt;/p&gt;

&lt;p&gt;And that's the part I find most interesting about backend engineering: a seemingly simple line of code can eventually lead all the way down to sockets, TCP, operating-system limits, and distributed-system failure modes.&lt;/p&gt;

</description>
      <category>go</category>
      <category>networking</category>
      <category>backenddevelopment</category>
    </item>
  </channel>
</rss>
