<?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: Gaddeti Rohit Babu</title>
    <description>The latest articles on DEV Community by Gaddeti Rohit Babu (@rohitbabugaddeti).</description>
    <link>https://dev.to/rohitbabugaddeti</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%2F390294%2Fb458d46c-b10a-4bba-a992-35779493ec23.jpeg</url>
      <title>DEV Community: Gaddeti Rohit Babu</title>
      <link>https://dev.to/rohitbabugaddeti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rohitbabugaddeti"/>
    <language>en</language>
    <item>
      <title>Threads Are Not Connections: A Spring Boot Concurrency Incident</title>
      <dc:creator>Gaddeti Rohit Babu</dc:creator>
      <pubDate>Fri, 16 Oct 2026 10:17:40 +0000</pubDate>
      <link>https://dev.to/rohitbabugaddeti/threads-are-not-connections-a-spring-boot-concurrency-incident-27lb</link>
      <guid>https://dev.to/rohitbabugaddeti/threads-are-not-connections-a-spring-boot-concurrency-incident-27lb</guid>
      <description>&lt;h2&gt;
  
  
  Description:
&lt;/h2&gt;

&lt;p&gt;A Spring Boot service slowed down under load because API calls and database calls shared one thread pool. How the team found it, fixed it, and why virtual threads would not have helped.&lt;/p&gt;

&lt;p&gt;Load went up, and the service slowed down with it. p99 latency climbed from 400 ms to 18 s. CPU sat around 20%. Connection timeout errors started showing up in the logs.&lt;/p&gt;

&lt;p&gt;A slow service usually has something working too hard. This one did not. Requests were piling up, yet very little real work was getting done. Most of the time was going to waiting, and the engineers had to find out what for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the service was doing
&lt;/h2&gt;

&lt;p&gt;Each unit of work was a short chain of blocking calls. Some went to external APIs. Some went to the database. A single unit could touch both, more than once, one after another.&lt;/p&gt;

&lt;p&gt;The service ran many units at the same time with &lt;code&gt;CompletableFuture&lt;/code&gt;. Every stage of every unit ran on one shared thread pool of 100 threads. Database access went through a Hikari connection pool with 20 connections.&lt;/p&gt;

&lt;p&gt;Both kinds of calls block the thread that makes them. A thread that sends an HTTP request sits idle until the response comes back. A thread that needs the database first asks Hikari for a connection. If none is free, it waits. Then it waits again for the query. While a thread waits, it does no useful work. It is also unavailable for anything else.&lt;/p&gt;

&lt;p&gt;So the 100 threads were one shared budget for two different kinds of waiting. When the APIs were fast and connections were free, the budget was enough. When either side slowed down, it spent the budget for both.&lt;/p&gt;

&lt;p&gt;That design was the team's own choice, and it was easy to set up. The problem was that 100 did not describe any real limit in the system. It was just a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the cause
&lt;/h2&gt;

&lt;p&gt;The team took thread dumps during the slowdown and lined them up with Hikari's metrics. Nearly every pool thread was blocked. Some were waiting on HTTP responses. Many more were parked inside &lt;code&gt;getConnection&lt;/code&gt;. Hikari showed all 20 connections in use and up to 60 threads waiting for one.&lt;/p&gt;

&lt;p&gt;A thread could get stuck in two ways, and each made the other worse.&lt;/p&gt;

&lt;p&gt;The first was slow API calls. Under load, external calls took longer and held their threads longer. That left fewer threads for every other stage, including database work, which sat in the executor's queue.&lt;/p&gt;

&lt;p&gt;The second was the database. With 100 threads and only 20 connections, most of the pool could end up waiting inside Hikari. A thread waiting for a connection is just as unavailable as one waiting on HTTP. API stages then queued behind them.&lt;/p&gt;

&lt;p&gt;Because both kinds of work drew from the same threads, slowness on one side became delay on the other. Every unit of work waited in the same queue, whatever resource it needed next.&lt;/p&gt;

&lt;p&gt;The root cause was a mismatch between thread demand and available connections. The thread pool and the connection pool had been sized as two separate settings. They should have been one decision. This also explained the low CPU. Blocked threads use almost none.&lt;/p&gt;

&lt;p&gt;Transaction scope made it worse. One code path opened a transaction, called an external API, and wrote the result before committing. The connection stayed checked out for the whole API call. In that path, every slow response took a connection out of the pool while no query was running.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: one executor per resource
&lt;/h2&gt;

&lt;p&gt;The team split the shared pool into two executors. One runs database work. The other runs external API calls. Each stage now runs on the executor for the resource it blocks on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExecutorConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 18 = Hikari maximum-pool-size (20) minus 2 for request-path queries&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolTaskExecutor&lt;/span&gt; &lt;span class="nf"&gt;dbExecutor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;  &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"db-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// 80 = ~200 calls/s x 0.3 s p95 latency = 60, plus headroom&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolTaskExecutor&lt;/span&gt; &lt;span class="nf"&gt;apiExecutor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolTaskExecutor&lt;/span&gt; &lt;span class="nf"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCorePoolSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMaxPoolSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setThreadNamePrefix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DB executor was resized to align with the Hikari pool. A database thread without a connection can only wait, so threads beyond the connection count add waiting and nothing else. With the sizes aligned, extra database work waits in the executor queue, where it holds no thread and no connection. The executor got 18 threads rather than 20, because a few request-path queries use the same Hikari pool outside the executor.&lt;/p&gt;

&lt;p&gt;The API executor needed a different method, and Little's Law gives it. In plain words, the calls in flight equal the calls started per second multiplied by the seconds each call takes. At peak, the service handled about 100 units of work per second, each making two API calls. That is about 200 calls per second. The API's p95 latency under load was around 300 ms, so about 60 calls are in flight at any moment. The team added headroom and set the executor to 80 threads.&lt;/p&gt;

&lt;p&gt;The total thread count barely changed, from 100 to 98. What changed was which work could use which threads.&lt;/p&gt;

&lt;p&gt;Latency is a live input here. If the API gets twice as slow, the same throughput needs twice as many threads. A call with no timeout breaks the math completely, because it can hold a thread for as long as the remote side likes.&lt;/p&gt;

&lt;p&gt;The stages then hop between executors explicitly. A simplified version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Job&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplyAsync&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;externalApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;apiExecutor&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thenApplyAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dbExecutor&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thenApplyAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;externalApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;confirm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;apiExecutor&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thenAcceptAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;markDone&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dbExecutor&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Async&lt;/code&gt; variants matter. Plain &lt;code&gt;thenApply&lt;/code&gt; can run the next stage on whatever thread completed the previous one. In this chain, that would quietly put database work back on an API thread.&lt;/p&gt;

&lt;p&gt;The split also assumes each stage touches one resource. The transactional path broke that rule, so the team moved its API call out of the transaction. The transaction now covers only the writes. They also added dashboards and alerts on pending connections and on each executor's queue depth.&lt;/p&gt;

&lt;p&gt;After the fix, p99 at peak load settled around 450 ms. Connection timeout errors stopped. Peak throughput went from about 70 units per second during the incident back to the expected 100.&lt;/p&gt;

&lt;p&gt;It worked because the two kinds of waiting stopped sharing a budget. A slow API can fill its own executor, but it cannot take threads from database work. A busy database cannot stall API stages. A slow API still lowers overall throughput, since every unit needs both. It just no longer drags the database side down with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would virtual threads have prevented this?
&lt;/h2&gt;

&lt;p&gt;Java 21 made virtual threads a standard feature. They are cheap, and a service can run a very large number of them. When a virtual thread blocks on I/O, it usually releases the platform thread underneath it. Spring Boot 3.2 added a &lt;code&gt;spring.threads.virtual.enabled&lt;/code&gt; property that moves several of its own components onto virtual threads on Java 21 or later.&lt;/p&gt;

&lt;p&gt;The incident was a story about blocked threads, so the question is fair. The answer is no. Virtual threads make threads cheap. They do nothing about connections, and there would still be only 20 of those.&lt;/p&gt;

&lt;p&gt;Picture the same service with one virtual thread per task, using &lt;code&gt;Executors.newVirtualThreadPerTaskExecutor()&lt;/code&gt;. The 100-thread limit is gone, and every unit starts&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>concurrency</category>
      <category>performance</category>
    </item>
    <item>
      <title>Notify via email when an event occurs in a website</title>
      <dc:creator>Gaddeti Rohit Babu</dc:creator>
      <pubDate>Wed, 20 May 2020 11:22:29 +0000</pubDate>
      <link>https://dev.to/rohitbabugaddeti/notify-via-email-when-an-event-occurs-in-a-website-33jg</link>
      <guid>https://dev.to/rohitbabugaddeti/notify-via-email-when-an-event-occurs-in-a-website-33jg</guid>
      <description>&lt;p&gt;Ever wanted to know as soon as &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a product comes into stock?&lt;/li&gt;
&lt;li&gt;tickets are available for any event?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think most of us have had those questions. After reading this, you'll know how to implement answers for the above questions/similar questions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rohitbabugaddeti/swiggy6-notifier"&gt;This&lt;/a&gt; implementation had paved ways to find answers to the above questions.&lt;/p&gt;

&lt;p&gt;The project mentioned above was built in last summer(2019 April) during the IPL season. What is IPL? &lt;a href="https://en.wikipedia.org/wiki/Indian_Premier_League"&gt;check here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main objective of this project is to notify the user when someone hits a SIX in the IPL match. With few modifications, you can build a system to notify yourself when an event occurs in a website.&lt;/p&gt;

&lt;p&gt;Diving into how I did it, the first and core concept I should discuss about is &lt;a href="https://en.wikipedia.org/wiki/Web_scraping"&gt;Web Scraping&lt;/a&gt;. Web Scraping allows you to get data from websites even if the website owner is not providing any API kind of thing. I used Python language in this project and will be talking about python packages throughout this post. Python has web scraping libraries like &lt;em&gt;BeautifulSoup, Scrapy&lt;/em&gt; etc. I used &lt;em&gt;BeautifulSoup&lt;/em&gt; here because it is simple and fits our need. &lt;/p&gt;

&lt;p&gt;BeautifulSoup consists of powerful parsers that can parse response objects into traversable objects. It also has several inbuilt methods to filter out the data extracted from the website. We can achieve a lot of things using them. &lt;/p&gt;

&lt;p&gt;So, you first need to make a request to the website you wanted to scrape using its URL. This can be done with packages like &lt;em&gt;urllib, urllib3, requests&lt;/em&gt; etc. You'll get a HttpResponse object from the server of the website you requested. Now you have to pass that response object to &lt;em&gt;BeautifulSoup&lt;/em&gt; class specifying which parser to use. It will give a parsed &lt;em&gt;bs4&lt;/em&gt; object accordingly.&lt;/p&gt;

&lt;p&gt;Now, you have to check for the required data from the parsed object using builtin methods or regular expressions.&lt;/p&gt;

&lt;p&gt;You'll want to notify after checking for the data. To notify the user via email, you need two packages: &lt;em&gt;smtplib, email&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;email&lt;/em&gt; is used to frame email object which we will be using in &lt;em&gt;sendmail&lt;/em&gt; method of &lt;em&gt;smtplib&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Using threads to send notifications can prevent the pausing of main activity.&lt;/p&gt;

</description>
      <category>python</category>
      <category>octograd2020</category>
    </item>
  </channel>
</rss>
