<?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: Rishav Paul</title>
    <description>The latest articles on DEV Community by Rishav Paul (@rishav_paul).</description>
    <link>https://dev.to/rishav_paul</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2018186%2F283a6d5c-cf7e-4aef-968d-4d704462651a.png</url>
      <title>DEV Community: Rishav Paul</title>
      <link>https://dev.to/rishav_paul</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rishav_paul"/>
    <language>en</language>
    <item>
      <title>Modernizing Java Monoliths for Better Performance with Async and Non-Blocking Architectures</title>
      <dc:creator>Rishav Paul</dc:creator>
      <pubDate>Fri, 15 Nov 2024 08:17:47 +0000</pubDate>
      <link>https://dev.to/rishav_paul/modernizing-java-monoliths-for-better-performance-with-async-and-non-blocking-architectures-1nef</link>
      <guid>https://dev.to/rishav_paul/modernizing-java-monoliths-for-better-performance-with-async-and-non-blocking-architectures-1nef</guid>
      <description>&lt;p&gt;In a recent project, I modernized an aging monolithic Java web service written in Dropwizard. This service handled a number of third-party (3P) dependencies through AWS Lambda functions, but performance was lagging due to the synchronous, blocking nature of the architecture. The setup had a P99 latency of 20 seconds, blocking request threads while waiting for the serverless functions to complete. This blocking caused thread pool saturation, leading to frequent request failures during peak traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identifying the Performance Bottleneck
&lt;/h3&gt;

&lt;p&gt;The crux of the issue was that each request to a Lambda function occupied a request thread in the Java service. Since these 3P functions often took considerable time to complete, the threads handling them would remain blocked, consuming resources and limiting scalability. Here’s an example of what this blocking behavior looks like in code:&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="c1"&gt;// Blocking code example&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;callLambdaService&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;payload&lt;/span&gt;&lt;span class="o"&gt;)&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;externalLambdaService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;response&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;In this example, the &lt;code&gt;callLambdaService&lt;/code&gt; method waits until &lt;code&gt;externalLambdaService.invoke()&lt;/code&gt; returns a response. Meanwhile, no other tasks can utilize the thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution: Migrating to Asynchronous, Non-Blocking Patterns
&lt;/h3&gt;

&lt;p&gt;To address these bottlenecks, I re-architected the service using asynchronous and non-blocking methods. This change involved using an HTTP client that invoked the Lambda functions to use &lt;code&gt;AsyncHttpClient&lt;/code&gt; from the &lt;code&gt;org.asynchttpclient&lt;/code&gt; library, which internally uses an &lt;code&gt;EventLoopGroup&lt;/code&gt; to handle requests asynchronously.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;AsyncHttpClient&lt;/code&gt; helped to offload blocking operations without consuming threads from the pool. Here’s an example of what the updated non-blocking call looks like:&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="c1"&gt;// Non-blocking code example&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callLambdaServiceAsync&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;payload&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="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;asyncHttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;h4&gt;
  
  
  Leveraging Java's CompletableFuture for Chaining Async Calls
&lt;/h4&gt;

&lt;p&gt;In addition to making individual calls non-blocking, I chained multiple dependency calls using &lt;code&gt;CompletableFuture&lt;/code&gt;. With methods like &lt;code&gt;thenCombine&lt;/code&gt; and &lt;code&gt;thenApply&lt;/code&gt;, I could asynchronously fetch and combine data from multiple sources, significantly boosting throughput.&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;future1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callLambdaServiceAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;future2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callLambdaServiceAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;combinedResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;future1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thenCombine&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;processResults&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result2&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;h3&gt;
  
  
  Introducing Type Safety with a Custom &lt;code&gt;SafeAsyncResponse&lt;/code&gt; Class
&lt;/h3&gt;

&lt;p&gt;During implementation, I observed that Java’s default &lt;code&gt;AsyncResponse&lt;/code&gt; object lacked type safety, allowing arbitrary Java objects to be passed around. To address this, I created a &lt;code&gt;SafeAsyncResponse&lt;/code&gt; class with generics, which ensured that only the specified response type could be returned, promoting maintainability and reducing the risk of runtime errors. This class also logs errors if a response is written more than once.&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SafeAsyncResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;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="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="no"&gt;LOGGER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SafeAsyncResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&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;final&lt;/span&gt; &lt;span class="nc"&gt;AsyncResponse&lt;/span&gt; &lt;span class="n"&gt;asyncResponse&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;final&lt;/span&gt; &lt;span class="nc"&gt;AtomicInteger&lt;/span&gt; &lt;span class="n"&gt;invocationCount&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;AtomicInteger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;SafeAsyncResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AsyncResponse&lt;/span&gt; &lt;span class="n"&gt;asyncResponse&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asyncResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncResponse&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Factory method to create a SafeAsyncResponse from an AsyncResponse.
     *
     * @param asyncResponse the AsyncResponse to wrap
     * @param &amp;lt;T&amp;gt;           the type of the response
     * @return a new instance of SafeAsyncResponse
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;SafeAsyncResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AsyncResponse&lt;/span&gt; &lt;span class="n"&gt;asyncResponse&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SafeAsyncResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncResponse&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Resume the async response with a successful result.
     *
     * @param response the successful response of type T
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;withSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invocationCount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;logError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"withSuccess"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;asyncResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resume&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Resume the async response with an error.
     *
     * @param error the throwable representing the error
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;withError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invocationCount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;logError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"withError"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;asyncResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resume&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Logs an error message indicating multiple invocations.
     *
     * @param methodName the name of the method that was invoked multiple times
     */&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;logError&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;methodName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;severe&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"SafeAsyncResponse method '%s' invoked more than once. Ignoring subsequent invocations."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&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;h4&gt;
  
  
  Sample Usage of SafeAsyncResponse
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GET&lt;/span&gt;
&lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/example"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;exampleEndpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Suspended&lt;/span&gt; &lt;span class="nc"&gt;AsyncResponse&lt;/span&gt; &lt;span class="n"&gt;asyncResponse&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;SafeAsyncResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;safeResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SafeAsyncResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncResponse&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Simulate success&lt;/span&gt;
    &lt;span class="n"&gt;safeResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Operation successful!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Simulate multiple invocations (only the first one will be processed)&lt;/span&gt;
    &lt;span class="n"&gt;safeResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withError&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;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This should not be processed"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;safeResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This will be ignored"&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;h3&gt;
  
  
  Testing and Performance Gains
&lt;/h3&gt;

&lt;p&gt;To verify the effectiveness of these changes, I wrote load tests using virtual threads to simulate maximum throughput on a single machine. I generated different levels of serverless function execution times (ranging from 1 to 20 seconds) and found that the new async non-blocking implementation increased throughput by 8x for lower execution times and by about 4x for higher execution times.&lt;/p&gt;

&lt;p&gt;In setting up these load tests, I made sure to adjust client-level connection limits to maximize throughput, which is essential to avoid bottlenecks in asynchronous systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovering a Hidden Bug in the HTTP Client
&lt;/h3&gt;

&lt;p&gt;While running these stress tests, I discovered a hidden bug in our custom HTTP client. The client used a semaphore with a connection timeout set to &lt;code&gt;Integer.MAX_VALUE&lt;/code&gt;, meaning if the client ran out of available connections, it would block the thread indefinitely. Resolving this bug was crucial to prevent potential deadlocks in high-load scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Choice Between Virtual Threads and Traditional Async Code
&lt;/h3&gt;

&lt;p&gt;One might wonder why we didn’t simply switch to virtual threads, which can reduce the need for asynchronous code by allowing threads to block without a significant resource cost. However, there’s a current limitation with virtual threads: they are pinned during synchronized operations. This means that when a virtual thread enters a synchronized block, it cannot unmount, potentially blocking OS resources until the operation completes.&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 java"&gt;&lt;code&gt;&lt;span class="kd"&gt;synchronized&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&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;nread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInputStream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Can block here&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;In this code, if &lt;code&gt;read&lt;/code&gt; blocks because there’s no data available, the virtual thread is pinned to an OS thread, preventing it from unmounting and blocking the OS thread as well. &lt;/p&gt;

&lt;p&gt;Fortunately, with JEP 491 on the horizon, Java developers can look forward to improved behavior for virtual threads, where blocking operations in synchronized code can be handled more efficiently without exhausting platform threads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Through refactoring our service to an async non-blocking architecture, we achieved significant performance improvements. By implementing &lt;code&gt;AsyncHttpClient&lt;/code&gt;, introducing &lt;code&gt;SafeAsyncResponse&lt;/code&gt; for type safety, and conducting load tests, we were able to optimize our Java service and greatly improve throughput. This project was a valuable exercise in modernizing monolithic applications and revealed the importance of proper async practices for scalability. &lt;/p&gt;

&lt;p&gt;As Java evolves, we may be able to leverage virtual threads more effectively in the future, but for now, async and non-blocking architecture remains an essential approach for performance optimization in high-latency, third-party-dependent services.&lt;/p&gt;

</description>
      <category>java</category>
      <category>serverless</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deploying an application to Amazon Elastic Kubernetes Service</title>
      <dc:creator>Rishav Paul</dc:creator>
      <pubDate>Tue, 03 Sep 2024 01:21:22 +0000</pubDate>
      <link>https://dev.to/rishav_paul/deploying-an-application-to-amazon-eks-id7</link>
      <guid>https://dev.to/rishav_paul/deploying-an-application-to-amazon-eks-id7</guid>
      <description>&lt;p&gt;So you woke up one morning and decided to learn Kubernetes. You dabbled a bit and discovered that it requires a lot of theoretical knowledge to get started, but you’re more of a doer.&lt;/p&gt;

&lt;p&gt;I'm on the same boat as you. And unlike other blogs or tutorials where you see the finished product working perfectly, these blogs are intended to document my real life journey building micro-services on EKS.&lt;/p&gt;

&lt;p&gt;Through the series of blog posts, I plan on building a personal finance app. We'll see how far we go. I've named the app &lt;code&gt;Finansy&lt;/code&gt;, which is Russian for Finance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Note on using AWS
&lt;/h3&gt;

&lt;p&gt;Make sure to enable MFA on your account and setup Billing alerts on AWS. This is a good &lt;a href="https://www.youtube.com/watch?v=FVwdlJ8lM0Q" rel="noopener noreferrer"&gt;Youtube video &lt;/a&gt; about Billing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an Kubernetes Cluster
&lt;/h3&gt;

&lt;p&gt;We will use &lt;a href="https://eksctl.io/getting-started/" rel="noopener noreferrer"&gt;eksctl&lt;/a&gt; for managing our EKS clusters. This is the most popular interface I could find.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;eksctl is a simple CLI tool for creating and managing clusters on EKS - Amazon's managed Kubernetes service for EC2. It is written in Go, uses CloudFormation, was created by Weaveworks and it welcomes contributions from the community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Note that because &lt;code&gt;eksctl&lt;/code&gt; uses CloudFormation, I'd highly recommend to NOT use the AWS console to modify the resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eksctl create cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; finansy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; 1.30 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--nodes&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--node-type&lt;/span&gt; t2.micro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-west-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requires 15 mins for cluster to initialize. Note that t2.micro is free tier eligible. My goal is to keep costs down as much as possible.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We will discover later that &lt;code&gt;t2.micro&lt;/code&gt; is not the right instance type. Use c5.large to avoid the extra work&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration to connect to the EKS Cluster from the terminal
&lt;/h3&gt;

&lt;p&gt;Now that the cluster is created, we'd want to connect to that from the CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks update-kubeconfig &lt;span class="nt"&gt;--region&lt;/span&gt; us-west-2 &lt;span class="nt"&gt;--name&lt;/span&gt; finansy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will enable us to use &lt;a href="https://kubernetes.io/docs/reference/kubectl/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt; interact with our Kubernetes cluster's control plane.&lt;/p&gt;

&lt;p&gt;You might be wondering why we'd need another CLI when we had &lt;code&gt;eksctl&lt;/code&gt; already. &lt;code&gt;eksctl&lt;/code&gt; is an AWS utility to manager your EKS cluster, but you can't configure K8S using &lt;code&gt;eksctl&lt;/code&gt;. We need &lt;code&gt;kubectl&lt;/code&gt; for that.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; Commands to get the current &lt;code&gt;kubectl&lt;/code&gt; context in case you are connected to clusters locally (&lt;em&gt;minikube&lt;/em&gt;) and remotely (&lt;em&gt;EKS&lt;/em&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl config current-context
kubectl config get-contexts
kubectl config use-context &amp;lt;your-eks-context-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a Java service
&lt;/h3&gt;

&lt;p&gt;We'll deploy a Java based API on Spring Boot to EKS. Here is a &lt;a href="https://spring.io/guides/gs/spring-boot" rel="noopener noreferrer"&gt;blog&lt;/a&gt; with the process in detail. Make sure you can access an API on port &lt;code&gt;8080&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I will share details about the web service in a separate blog post soon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating a JAR
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;and copying it to a infra related project directory&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Modify the &lt;code&gt;build&lt;/code&gt; tag to your &lt;code&gt;pom.xml&lt;/code&gt; file asking maven to generate a &lt;code&gt;jar&lt;/code&gt; file. Note that the &lt;code&gt;copy&lt;/code&gt; configuration helps put the &lt;code&gt;jar&lt;/code&gt; in a different folder for managing k8s infra, but that's purely optional.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;repackage&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-antrun-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.8&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;package&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;run&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;target&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;copy&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"${project.build.directory}/${project.artifactId}.jar"&lt;/span&gt;
                              &lt;span class="na"&gt;tofile=&lt;/span&gt;&lt;span class="s"&gt;"app/${project.artifactId}.jar"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;finalName&amp;gt;&lt;/span&gt;${project.artifactId}&lt;span class="nt"&gt;&amp;lt;/finalName&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a Dockerfile
&lt;/h3&gt;

&lt;p&gt;Filename: &lt;code&gt;Dockerfile&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Use an official OpenJDK 21 runtime as a parent image
FROM openjdk:21

# Set the working directory in the container
WORKDIR /app

# Copy the executable JAR file to the container
COPY app/portfolio-service.jar /app/portfolio-service.jar

CMD ["java", "-jar", "/app/portfolio-service.jar"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we are simply generating a docker image using the jar generated by maven. You may want want to run this container and make sure that the service works as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t finansy/portfolio-service:1.1 .
docker run -d -p 8080:8080 --name portfolio-service finansy/portfolio-service:1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;curl http://localhost:8080/&amp;lt;path&amp;gt;&lt;/code&gt; and ensure you get an expected response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upload Image to Amazon ECR
&lt;/h3&gt;

&lt;p&gt;What is Amazon Elastic Container Register?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Push container images to Amazon ECR without installing or scaling infrastructure, and pull images using any management tool.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We would like to push our image to ECR to enable EKS to refer to it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the Amazon ECR console &lt;/li&gt;
&lt;li&gt;Create a repository&lt;/li&gt;
&lt;li&gt;Click the View push commands button in the repository details &lt;/li&gt;
&lt;li&gt;Follow the instructions to authenticate Docker with the ECR registry &lt;/li&gt;
&lt;li&gt;Push the Docker image&lt;/li&gt;
&lt;li&gt;Copy the docker image uri, and update the &lt;code&gt;image&lt;/code&gt; path below in &lt;code&gt;k8s.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kubernetes Concepts in Brief
&lt;/h3&gt;

&lt;p&gt;Here’s a brief overview of Kubernetes concepts that will be useful for rest of the post. &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. Kubernetes Pod&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A &lt;strong&gt;Pod&lt;/strong&gt; is the smallest and simplest Kubernetes object. It represents a single instance of a running process in your cluster. A Pod can encapsulate one or more containers (e.g., Docker containers) that share the same network namespace, IP address, and storage volumes. Containers in a Pod are typically tightly coupled and need to share resources, such as storage or networking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Characteristics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single IP Address&lt;/strong&gt;: Each Pod has a unique IP address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared Storage&lt;/strong&gt;: Containers in the same Pod share storage volumes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lifecycle&lt;/strong&gt;: Pods are designed to be ephemeral. They can be created and destroyed dynamically based on the needs of the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2. Kubernetes Deployment&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A &lt;strong&gt;Deployment&lt;/strong&gt; is a higher-level abstraction that manages the deployment and scaling of Pods. It ensures that a specified number of Pods are running and updates them in a controlled manner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Functions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scaling&lt;/strong&gt;: Easily scale the number of Pods up or down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rolling Updates&lt;/strong&gt;: Perform rolling updates to update Pods without downtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback&lt;/strong&gt;: Revert to a previous version if an update fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Typical Use&lt;/strong&gt;: You use Deployments to manage stateless applications and ensure that the desired state of the application is maintained.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;3. Kubernetes Service&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A &lt;strong&gt;Service&lt;/strong&gt; is an abstraction that defines a logical set of Pods and a policy by which to access them. It provides a stable endpoint (IP address and DNS name) for Pods, which can change over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Functions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Service Discovery&lt;/strong&gt;: Provides a stable DNS name for a set of Pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load Balancing&lt;/strong&gt;: Distributes incoming traffic among the Pods that are part of the Service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port Forwarding&lt;/strong&gt;: Maps a port on the Service to a port on the Pods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Types&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ClusterIP&lt;/strong&gt;: Exposes the Service on a cluster-internal IP. Only reachable within the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NodePort&lt;/strong&gt;: Exposes the Service on each node’s IP at a static port. Allows external access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LoadBalancer&lt;/strong&gt;: Provisioned by a cloud provider to expose the Service externally using a cloud-based load balancer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless Service&lt;/strong&gt;: Does not allocate a cluster IP. Useful for stateful applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;4. Kubernetes Load Balancer&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A &lt;strong&gt;Load Balancer&lt;/strong&gt; is not a Kubernetes resource itself but is often used in conjunction with Kubernetes Services. When you create a Service of type &lt;code&gt;LoadBalancer&lt;/code&gt;, Kubernetes provisions a cloud-based load balancer (if supported by your cloud provider) to manage external traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Functions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;External Access&lt;/strong&gt;: Provides a single point of access to the Services from outside the Kubernetes cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic Distribution&lt;/strong&gt;: Distributes incoming traffic across multiple Pods to balance the load.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Relationship Between Load Balancer and Service&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Load Balancer&lt;/strong&gt;: Is a resource provided by cloud providers (e.g., AWS ELB, GCP Load Balancer) that handles incoming traffic from outside the cluster and distributes it to the appropriate endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service (Type: LoadBalancer)&lt;/strong&gt;: When you create a Kubernetes Service of type &lt;code&gt;LoadBalancer&lt;/code&gt;, Kubernetes interacts with the cloud provider to create and configure the load balancer. The Service then uses this load balancer to route external traffic to the Pods that are part of the Service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary, while the &lt;strong&gt;Service&lt;/strong&gt; provides a stable internal interface and may manage traffic within the cluster, the &lt;strong&gt;Load Balancer&lt;/strong&gt; (when used with a &lt;code&gt;LoadBalancer&lt;/code&gt; Service type) provides an external interface and handles the distribution of traffic from outside the cluster to the appropriate Pods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create K8S configuration
&lt;/h3&gt;

&lt;p&gt;Create a new &lt;code&gt;k8s.yaml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;portfolio-service-deployment&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;portfolio-service&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;portfolio-service&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;portfolio-service&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;aws-account-id&amp;gt;.dkr.ecr.us-west-2.amazonaws.com/finansy/portfolio-service:latest&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;portfolio-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;portfolio-service&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a good time to catch up on Deployment and LoadBalancer Kuberntes component types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update cluster with our service and deployment configuration.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  portfolio-service kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s.yaml 
deployment.apps/portfolio-service-deployment created
service/portfolio-service created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Get Service Endpoint
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  portfolio-service kubectl get svc
NAME                TYPE           CLUSTER-IP      EXTERNAL-IP                                                               PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;        AGE
kubernetes          ClusterIP      10.100.0.1      &amp;lt;none&amp;gt;                                                                    443/TCP        26m
portfolio-service   LoadBalancer   10.100.177.55   a39b306d52a8b44fbbedcf670da443f6-1356185046.us-west-2.elb.amazonaws.com   80:31295/TCP   4m14s

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Get Pod Status
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  portfolio-service kubectl get pods
NAME                                            READY   STATUS    RESTARTS   AGE
portfolio-service-deployment-6b6b4b6c4c-6dm8w   0/1     Pending   0          5m6s
portfolio-service-deployment-6b6b4b6c4c-jtpnq   0/1     Pending   0          5m6s
portfolio-service-deployment-6b6b4b6c4c-r9xps   0/1     Pending   0          5m6s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They should be in the Running state. Let's look at our EKS page in AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why are pods in Pending state
&lt;/h3&gt;

&lt;p&gt;Seems like we exceeded the max IP limit per node.&lt;/p&gt;

&lt;p&gt;Found this interesting piece of information on Stack Overflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The formula for defining the maximum number of Pods per EC2 Node instance is as follows:

N * (M-1) + 2

Where:

N is the number of Elastic Network Interfaces (ENI) of the instance type

M is the number of IP addresses per ENI

So for the instance you used which is t3.micro the number of pods that can be deployed are:

2 * (2-1) + 2 = 4 Pods, the 4 pods capacity is already used by pods in kube-system namespace

Here you can find the calculated max number of pods for each instance type
https://github.com/aws/amazon-vpc-cni-k8s/blob/master/misc/eni-max-pods.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Identifying the right instance type for our use case
&lt;/h3&gt;

&lt;p&gt;We probably want to be able to run 15-20 pods per instance.&lt;/p&gt;

&lt;p&gt;Given these options, &lt;code&gt;c5.large&lt;/code&gt; and &lt;code&gt;m5.large&lt;/code&gt; are suitable as they provide enough IPs to support 15-20 pods and are relatively affordable. Between these two, the &lt;code&gt;c5.large&lt;/code&gt; instance is slightly cheaper.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;c5.large: 29 IPs, approximately $0.085 per hour&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We'll have to go ahead and update the nodes in our cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a new node group in the cluster
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  portfolio-service eksctl create nodegroup \
  --cluster finansy \
  --name c5LargeNg \
  --node-type c5.large \
  --nodes 1 \
  --nodes-min 1 \
  --nodes-max 1


2024-09-02 12:02:22 [ℹ]  will use version 1.30 for new nodegroup(s) based on control plane version
2024-09-02 12:02:24 [ℹ]  nodegroup "c5LargeNg" will use "" [AmazonLinux2/1.30]
2024-09-02 12:02:24 [ℹ]  2 existing nodegroup(s) (new-node-group,ng-9e92b41d) will be excluded
2024-09-02 12:02:24 [ℹ]  1 nodegroup (c5LargeNg) was included (based on the include/exclude rules)
2024-09-02 12:02:24 [ℹ]  will create a CloudFormation stack for each of 1 managed nodegroups in cluster "finansy"
2024-09-02 12:02:24 [ℹ]
2 sequential tasks: { fix cluster compatibility, 1 task: { 1 task: { create managed nodegroup "c5LargeNg" } }
}
2024-09-02 12:02:24 [ℹ]  checking cluster stack for missing resources
2024-09-02 12:02:25 [ℹ]  cluster stack has all required resources
2024-09-02 12:02:25 [ℹ]  building managed nodegroup stack "eksctl-finansy-nodegroup-c5LargeNg"
2024-09-02 12:02:25 [ℹ]  deploying stack "eksctl-finansy-nodegroup-c5LargeNg"
2024-09-02 12:02:25 [ℹ]  waiting for CloudFormation stack "eksctl-finansy-nodegroup-c5LargeNg"
2024-09-02 12:02:55 [ℹ]  waiting for CloudFormation stack "eksctl-finansy-nodegroup-c5LargeNg"
2024-09-02 12:03:52 [ℹ]  waiting for CloudFormation stack "eksctl-finansy-nodegroup-c5LargeNg"
2024-09-02 12:05:17 [ℹ]  waiting for CloudFormation stack "eksctl-finansy-nodegroup-c5LargeNg"
2024-09-02 12:05:17 [ℹ]  no tasks
2024-09-02 12:05:17 [✔]  created 0 nodegroup(s) in cluster "finansy"
2024-09-02 12:05:18 [ℹ]  nodegroup "c5LargeNg" has 1 node(s)
2024-09-02 12:05:18 [ℹ]  node "ip-192-168-3-155.us-west-2.compute.internal" is ready
2024-09-02 12:05:18 [ℹ]  waiting for at least 1 node(s) to become ready in "c5LargeNg"
2024-09-02 12:05:18 [ℹ]  nodegroup "c5LargeNg" has 1 node(s)
2024-09-02 12:05:18 [ℹ]  node "ip-192-168-3-155.us-west-2.compute.internal" is ready
2024-09-02 12:05:18 [✔]  created 1 managed nodegroup(s) in cluster "finansy"
2024-09-02 12:05:18 [ℹ]  checking security group configuration for all nodegroups
2024-09-02 12:05:18 [ℹ]  all nodegroups have up-to-date cloudformation templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete the old node group
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  ~ eksctl delete nodegroup --cluster finansy --name ng-9e92b41d
2024-09-02 12:06:03 [ℹ]  1 nodegroup (ng-9e92b41d) was included (based on the include/exclude rules)
2024-09-02 12:06:03 [ℹ]  will drain 1 nodegroup(s) in cluster "finansy"
2024-09-02 12:06:03 [ℹ]  starting parallel draining, max in-flight of 1
2024-09-02 12:06:03 [ℹ]  cordon node "ip-192-168-88-252.us-west-2.compute.internal"
2024-09-02 12:06:30 [✔]  drained all nodes: [ip-192-168-88-252.us-west-2.compute.internal]
2024-09-02 12:06:30 [✖]  failed to acquire semaphore while waiting for all routines to finish: context canceled
2024-09-02 12:06:30 [ℹ]  will delete 1 nodegroups from cluster "finansy"
2024-09-02 12:06:30 [ℹ]  1 task: { 1 task: { delete nodegroup "ng-9e92b41d" [async] } }
2024-09-02 12:06:31 [ℹ]  will delete stack "eksctl-finansy-nodegroup-ng-9e92b41d"
2024-09-02 12:06:31 [✔]  deleted 1 nodegroup(s) from cluster "finansy"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check the Pod logs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  ~ kubectl logs portfolio-service-deployment-6b6b4b6c4c-cslnw

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.2)

2024-09-02T19:08:16.805Z  INFO 1 --- [Finansy Portfolio Service] [           main] f.p.s.FinansyPortfolioServiceApplication : Starting FinansyPortfolioServiceApplication using Java 21 with PID 1 (/app/portfolio-service.jar started by root in /app)
2024-09-02T19:08:16.828Z  INFO 1 --- [Finansy Portfolio Service] [           main] f.p.s.FinansyPortfolioServiceApplication : No active profile set, falling back to 1 default profile: "default"
2024-09-02T19:08:20.090Z  INFO 1 --- [Finansy Portfolio Service] [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2024-09-02T19:08:20.940Z  INFO 1 --- [Finansy Portfolio Service] [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 828 ms. Found 1 R2DBC repository interface.
2024-09-02T19:08:24.691Z DEBUG 1 --- [Finansy Portfolio Service] [           main] s.w.r.r.m.a.RequestMappingHandlerMapping : 5 mappings in 'requestMappingHandlerMapping'
2024-09-02T19:08:24.728Z DEBUG 1 --- [Finansy Portfolio Service] [           main] o.s.w.r.handler.SimpleUrlHandlerMapping  : Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2024-09-02T19:08:24.824Z DEBUG 1 --- [Finansy Portfolio Service] [           main] o.s.w.r.r.m.a.ControllerMethodResolver   : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 @ExceptionHandler
2024-09-02T19:08:24.907Z DEBUG 1 --- [Finansy Portfolio Service] [           main] o.s.w.s.adapter.HttpWebHandlerAdapter    : enableLoggingRequestDetails='false': form data and headers will be masked to prevent unsafe logging of potentially sensitive data
2024-09-02T19:08:26.568Z  INFO 1 --- [Finansy Portfolio Service] [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080 (http)
2024-09-02T19:08:26.615Z  INFO 1 --- [Finansy Portfolio Service] [           main] f.p.s.FinansyPortfolioServiceApplication : Started FinansyPortfolioServiceApplication in 11.949 seconds (process running for 13.818)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Get Service Endpoint Again
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  ~ kubectl get svc

NAME                TYPE           CLUSTER-IP      EXTERNAL-IP                                                               PORT(S)        AGE
kubernetes          ClusterIP      10.100.0.1      &amp;lt;none&amp;gt;                                                                    443/TCP        88m
portfolio-service   LoadBalancer   10.100.177.55   a39b306d52a8b44fbbedcf670da443f6-1356185046.us-west-2.elb.amazonaws.com   80:31295/TCP   66m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query the endpoint
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  ~  curl http://a39b306d52a8b44fbbedcf670da443f6-1356185046.us-west-2.elb.amazonaws.com/api/v1/user-assets
[]%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is up and running!&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete the cluster
&lt;/h3&gt;

&lt;p&gt;To avoid incurring any additional costs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eksctl delete cluster --name finansy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>kubernetes</category>
      <category>aws</category>
      <category>eks</category>
      <category>java</category>
    </item>
  </channel>
</rss>
