<?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: William Nogueira</title>
    <description>The latest articles on DEV Community by William Nogueira (@williamnogueira).</description>
    <link>https://dev.to/williamnogueira</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%2F1106696%2F84eeb5c1-55bf-4ca5-a957-9a1488950bcb.jpg</url>
      <title>DEV Community: William Nogueira</title>
      <link>https://dev.to/williamnogueira</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/williamnogueira"/>
    <language>en</language>
    <item>
      <title>Scaling Java with Write-Behind Caching</title>
      <dc:creator>William Nogueira</dc:creator>
      <pubDate>Wed, 24 Dec 2025 03:02:17 +0000</pubDate>
      <link>https://dev.to/williamnogueira/scaling-java-with-write-behind-caching-fnd</link>
      <guid>https://dev.to/williamnogueira/scaling-java-with-write-behind-caching-fnd</guid>
      <description>&lt;p&gt;If you ask a Junior Developer to build a URL Shortener (like Bit.ly) that tracks click analytics, the logic usually looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; User clicks the short link (&lt;code&gt;/abc&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; App fetches the original URL from the database.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;App increments the click counter in the database (&lt;code&gt;UPDATE urls SET clicks = clicks + 1 ...&lt;/code&gt;).&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; App redirects the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This works great for 100 users. It works okay for 1,000 users.&lt;br&gt;
But at &lt;strong&gt;10,000 requests per second (RPS)&lt;/strong&gt;? Your database locks up, latency spikes, and your application crashes.&lt;/p&gt;

&lt;p&gt;Why? Because &lt;strong&gt;Database I/O is expensive&lt;/strong&gt;. Blocking a thread to write a "+1" to disk for every single click is a massive waste of resources.&lt;/p&gt;

&lt;p&gt;In this article, I’ll show you how I solved this using the &lt;strong&gt;Write-Behind Caching&lt;/strong&gt; pattern, &lt;strong&gt;Redis&lt;/strong&gt;, and &lt;strong&gt;Java 25 Virtual Threads&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Bottleneck: Synchronous Writes
&lt;/h2&gt;

&lt;p&gt;In the naive approach, the "Read" (finding the URL) and the "Write" (tracking the click) are coupled.&lt;/p&gt;

&lt;p&gt;If your database takes 50ms to perform the update, your maximum throughput per thread is capped. If traffic spikes, your connection pool exhausts, and requests start timing out.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution: Write-Behind Caching
&lt;/h2&gt;

&lt;p&gt;To achieve high scale, we need to &lt;strong&gt;decouple&lt;/strong&gt; the analytics from the redirect. The user wants to go to Google.com; they don't care if we counted their click &lt;em&gt;right now&lt;/em&gt; or 5 minutes from now.&lt;/p&gt;

&lt;p&gt;Here is the architecture I implemented in my project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Fast Path:&lt;/strong&gt; Increment the counter in &lt;strong&gt;Redis&lt;/strong&gt; (Memory is fast!).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dirty Tracking:&lt;/strong&gt; Add the Short Code to a "Dirty Set" in Redis so we know which records changed.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Slow Path (Async):&lt;/strong&gt; A background scheduler wakes up, reads the Dirty Set, and flushes the counts to the Database (DynamoDB/Postgres) in &lt;strong&gt;batches&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's look at the code.&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 1: The "Fire and Forget" Increment
&lt;/h3&gt;

&lt;p&gt;I used &lt;strong&gt;Spring Boot 4&lt;/strong&gt; and &lt;strong&gt;RedisTemplate&lt;/strong&gt;. When a request comes in, we hit Redis. This operation takes sub-milliseconds.&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;@Async&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clickExecutor"&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;incrementClickCount&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;code&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Atomic Increment in Redis (Super fast)&lt;/span&gt;
    &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clicks:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Mark this code as "Dirty" so the scheduler knows it changed&lt;/span&gt;
    &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForSet&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dirty_urls"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&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;Notice the &lt;code&gt;@Async&lt;/code&gt;. The controller returns the redirect immediately. This logic ensures the user experiences zero latency penalty for our analytics tracking:&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="nd"&gt;@EnableAsync&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AsyncConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"clickExecutor"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Executor&lt;/span&gt; &lt;span class="nf"&gt;clickExecutor&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="nf"&gt;TaskExecutorAdapter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&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;h3&gt;
  
  
  Step 2: The Virtual Thread Scheduler
&lt;/h3&gt;

&lt;p&gt;This is where &lt;strong&gt;Java 25&lt;/strong&gt; shines.&lt;/p&gt;

&lt;p&gt;Historically, background tasks that talk to databases were heavy because they blocked OS threads. With &lt;strong&gt;Project Loom (Virtual Threads)&lt;/strong&gt;, we can spawn threads that are lightweight. If the database is slow, the Virtual Thread "parks" itself, freeing up the carrier thread to do other work.&lt;/p&gt;

&lt;p&gt;Here is the scheduler that runs every 5 minutes:&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;@Scheduled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cron&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0 */5 * * * *"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Runs every 5 mins&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;persistClicks&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting sync job..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Pop a batch of codes (e.g., 1000 at a time) from the Set&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForSet&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;pop&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dirty_urls"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&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="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Process in parallel using Virtual Threads&lt;/span&gt;
        &lt;span class="k"&gt;try&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;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&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;syncToDb&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&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;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: The Atomic Sync
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;syncToDb&lt;/code&gt; method is responsible for moving the data from Cache to Storage.&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;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;syncToDb&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;code&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Get the count from Redis and RESET it to 0 atomically&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clickCountObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAndSet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clicks:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0"&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;clickCountObj&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&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="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;clicks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clickCountObj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&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;clicks&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 2. Update the Database (in this case, DynamoDB)&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;updateClickCount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clicks&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;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateClickCount&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;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;totalClicks&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dynamoDbClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateItem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"urls"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AttributeValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateExpression&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ADD clicks :inc"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;expressionAttributeValues&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;":inc"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="nc"&gt;AttributeValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="o"&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;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totalClicks&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&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;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Trade-off: What about Data Loss?
&lt;/h3&gt;

&lt;p&gt;In distributed systems, everything is a trade-off. By using Write-Behind, we gain massive performance, but we introduce a risk: &lt;strong&gt;Durability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the Redis instance crashes completely &lt;em&gt;before&lt;/em&gt; the 5-minute sync triggers, we lose the clicks that happened in that window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is this acceptable?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Business Value:&lt;/strong&gt; Losing 5 minutes of click analytics is usually acceptable. Losing the &lt;em&gt;actual URL mappings&lt;/em&gt; (Create operation) is not. (Creation is synchronous; Analytics is asynchronous).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Mitigation:&lt;/strong&gt; We can enable Redis AOF (Append Only File) persistence to minimize data loss on crash.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Results
&lt;/h3&gt;

&lt;p&gt;By implementing this pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Latency:&lt;/strong&gt; The &lt;code&gt;GET /{code}&lt;/code&gt; endpoint response time dropped from &lt;strong&gt;~60ms&lt;/strong&gt; (DB write) to &lt;strong&gt;~5ms&lt;/strong&gt; (Redis write).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Throughput:&lt;/strong&gt; The database is no longer hammered by every single click. It only receives bulk updates every few minutes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cost:&lt;/strong&gt; We reduced Database Write Units (WCUs), which is the most expensive part of cloud databases like DynamoDB.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;You don't always need microservices to scale. Sometimes, you just need to stop treating your database like a notepad. Patterns like &lt;strong&gt;Write-Behind Caching&lt;/strong&gt;, combined with modern tools like &lt;strong&gt;Virtual Threads&lt;/strong&gt;, allow monoliths to handle massive loads efficiently.&lt;/p&gt;

&lt;p&gt;You can check out the full implementation (including the Docker Compose setup and Testcontainers integration) in the repository below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/William-Nogueira/spring-url-shortener" rel="noopener noreferrer"&gt;GitHub Repo: Distributed URL Shortener&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>performance</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Comparing Database Management Systems: MySQL vs PostgreSQL</title>
      <dc:creator>William Nogueira</dc:creator>
      <pubDate>Sat, 02 Sep 2023 19:08:04 +0000</pubDate>
      <link>https://dev.to/williamnogueira/comparing-rdbms-mysql-vs-postgresql-5515</link>
      <guid>https://dev.to/williamnogueira/comparing-rdbms-mysql-vs-postgresql-5515</guid>
      <description>&lt;p&gt;If you're a beginner looking to dive into the world of relational database management systems (RDBMS), you may find yourself faced with the question of which one to learn first. Two popular options in the RDBMS landscape are MySQL and PostgreSQL. In this article, we will explore the key differences between these two systems, their areas of application, and help you decide which one might be the best fit for your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of MySQL and PostgreSQL
&lt;/h2&gt;

&lt;p&gt;MySQL and PostgreSQL are both open-source RDBMS that have been widely adopted by developers and organizations around the world. While they share some similarities, they also have distinct characteristics that set them apart. Let's take a closer look at each of these database systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgxshwy2np0unci2cguj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgxshwy2np0unci2cguj.png" alt="MySQL and PostgreSQL" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MySQL
&lt;/h3&gt;

&lt;p&gt;MySQL has a long history and is known for its simplicity and ease of use. It was initially developed by Michael Widenius and David Axmark in 1994 and has since become one of the most popular RDBMS options available. MySQL is particularly favored in web development, thanks to its seamless integration with popular web technologies like PHP. It offers a wide range of features, including support for multiple storage engines, replication, and high availability.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL
&lt;/h3&gt;

&lt;p&gt;PostgreSQL, often referred to as Postgres, is a powerful and feature-rich RDBMS that has gained a strong following in the developer community. Initially developed at the University of California, Berkeley, in 1985, PostgreSQL has evolved into a robust and highly extensible database system. It boasts advanced features such as support for complex data types, JSON, and full-text search. PostgreSQL is known for its adherence to SQL standards and its ability to handle complex queries and large amounts of data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Differences between MySQL and PostgreSQL
&lt;/h2&gt;

&lt;p&gt;While MySQL and PostgreSQL are both excellent choices for managing relational databases, they have some fundamental differences that may influence your decision. Let's examine these differences in more detail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2vucisittrgg5q3t7zf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2vucisittrgg5q3t7zf.png" alt="MySQL vs PostgreSQL" width="600" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Types and Flexibility
&lt;/h3&gt;

&lt;p&gt;One significant distinction between MySQL and PostgreSQL lies in their approach to data types. MySQL is a purely relational database, meaning it primarily deals with structured data. On the other hand, PostgreSQL is an object-relational database, which allows for the storage and manipulation of more complex data structures. PostgreSQL supports features such as arrays, hstore, and even user-defined data types, making it a more versatile choice for developers working with diverse data formats.&lt;/p&gt;

&lt;h3&gt;
  
  
  ACID Compliance
&lt;/h3&gt;

&lt;p&gt;ACID compliance is crucial for ensuring data integrity and consistency in a database. MySQL offers ACID compliance when used with certain storage engines like InnoDB and NDB Cluster. In contrast, PostgreSQL is fully ACID compliant in all configurations. This means that PostgreSQL guarantees transactional integrity, even in complex scenarios, making it a reliable choice for applications that require strict data consistency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency Control
&lt;/h3&gt;

&lt;p&gt;Concurrency control is essential for handling multiple simultaneous database operations. MySQL uses a locking mechanism for concurrency control, which can sometimes result in performance bottlenecks when dealing with high levels of concurrent transactions. PostgreSQL, on the other hand, employs a more advanced approach called Multiversion Concurrency Control (MVCC). MVCC allows for better concurrency by creating duplicate copies of records, enabling multiple users to read and update the same data simultaneously without conflicts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Indexing Options
&lt;/h3&gt;

&lt;p&gt;Indexes play a crucial role in optimizing database performance by enabling faster data retrieval. MySQL supports B-tree and R-tree indexes, which are suitable for hierarchical and spatial data, respectively. PostgreSQL offers a wider range of indexing options, including B-tree, expression indexes, partial indexes, and hash indexes. This flexibility allows developers to fine-tune their database performance based on specific requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Views and Materialized Views
&lt;/h3&gt;

&lt;p&gt;Views are virtual tables derived from the result of a query. They provide a convenient way to organize and access data from multiple tables. While both MySQL and PostgreSQL support views, PostgreSQL takes it a step further with materialized views. Materialized views are precomputed views that store the results of a query as a physical table. This can significantly improve query performance, especially when dealing with complex calculations or aggregations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stored Procedures and Triggers
&lt;/h3&gt;

&lt;p&gt;Stored procedures and triggers are essential tools for implementing complex business logic within a database. Both MySQL and PostgreSQL support stored procedures, which are reusable blocks of code that can be called from within the database. However, PostgreSQL has an advantage in that it allows stored procedures to be written in languages other than SQL, providing greater flexibility and enabling developers to leverage their preferred programming languages.&lt;/p&gt;

&lt;p&gt;Triggers, on the other hand, are database actions that automatically execute in response to specific events. MySQL supports triggers for SQL INSERT, UPDATE, and DELETE statements. PostgreSQL goes a step further by offering INSTEAD OF triggers, which allow for more complex SQL statements using functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing the Right RDBMS for Your Needs
&lt;/h2&gt;

&lt;p&gt;Now that we've explored the key differences between MySQL and PostgreSQL, you may be wondering which one is the best choice for you as a beginner. The answer ultimately depends on your specific requirements and the nature of the projects you'll be working on.&lt;/p&gt;

&lt;p&gt;If you're looking for a lightweight and easy-to-use database system with seamless integration into web development frameworks, MySQL may be the better option. Its simplicity and wide adoption make it an excellent choice for beginners and small to medium-sized projects.&lt;/p&gt;

&lt;p&gt;On the other hand, if you're working on more complex applications that require advanced features, support for diverse data types, and robust transactional capabilities, PostgreSQL may be the preferred choice. Its adherence to SQL standards and extensive feature set make it well-suited for enterprise-level applications and developers seeking maximum flexibility and control.&lt;/p&gt;

</description>
      <category>database</category>
      <category>mysql</category>
      <category>postgres</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Problem of Bloat in Web Development: Do We Really Need Front-End Frameworks?</title>
      <dc:creator>William Nogueira</dc:creator>
      <pubDate>Fri, 23 Jun 2023 07:26:54 +0000</pubDate>
      <link>https://dev.to/williamnogueira/the-problem-of-bloat-in-web-development-do-we-really-need-front-end-frameworks-14ma</link>
      <guid>https://dev.to/williamnogueira/the-problem-of-bloat-in-web-development-do-we-really-need-front-end-frameworks-14ma</guid>
      <description>&lt;p&gt;In recent years, the web development industry has witnessed a surge in the use of front-end frameworks for building web applications. These frameworks, such as Angular, React, and Vue.js, offer developers a plethora of tools and functionalities to streamline the development process. However, with the increasing popularity of these frameworks, concerns have been raised about the issue of bloat in web development. This article aims to explore the problem of bloat and question the necessity of front-end frameworks. Is it possible to build efficient and user-friendly web applications without relying on these frameworks? Let's delve deeper and find out.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Bloat in Web Development
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Growing Size of Web Pages
&lt;/h3&gt;

&lt;p&gt;Over the years, web pages have become increasingly bloated in terms of size. This bloat can be attributed to various factors, including the excessive use of JavaScript libraries, heavy media content, and overcomplicated design elements. To put things into perspective, let's take a look at the average size of web pages over the past decade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;March 2011: Average size 735kB&lt;/li&gt;
&lt;li&gt;March 2012: Average size 1008kB&lt;/li&gt;
&lt;li&gt;March 2013: Average size 1335kB&lt;/li&gt;
&lt;li&gt;March 2014: Average size 1728kB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bgw2ceylfvyjc9rlr5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bgw2ceylfvyjc9rlr5p.png" alt="Page Bloat" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see, there has been a significant increase in the average size of web pages, indicating a growing problem of bloat. This bloat not only affects the loading speed of websites but also leads to a poor user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Role of Front-End Frameworks in Bloat
&lt;/h3&gt;

&lt;p&gt;Front-end frameworks, while offering numerous advantages in terms of development speed and code organization, can contribute to the problem of bloat in web development. These frameworks often come bundled with a plethora of features and functionalities, many of which may not be necessary for every project. This results in unnecessary code bloat, leading to larger file sizes and slower loading times.&lt;br&gt;
Additionally, front-end frameworks often rely heavily on JavaScript, which can further contribute to the bloat. JavaScript files can be large and resource-intensive, especially when multiple libraries and dependencies are included. This not only affects the performance of the web application but also increases the load on the user's device, particularly on mobile devices with limited resources.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Case for Static Pages
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Simplicity and Efficiency of Static Pages
&lt;/h3&gt;

&lt;p&gt;Static pages, built using basic HTML and CSS, offer a simpler and more efficient approach to web development. By eliminating the need for complex JavaScript frameworks, static pages can significantly reduce the bloat and improve the overall performance of a website. With static pages, the focus is on delivering content quickly and efficiently, without unnecessary bells and whistles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5u5easj7tvz3wunlv4js.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5u5easj7tvz3wunlv4js.png" alt="Static Website" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Advantages of Using Basic HTML and CSS
&lt;/h3&gt;

&lt;p&gt;Building web pages with basic HTML and CSS has several advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lightweight and Fast:&lt;/strong&gt; Static pages are lightweight and load quickly, providing a seamless user experience. Without the overhead of complex JavaScript frameworks, the page size is significantly reduced, resulting in faster load times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better SEO:&lt;/strong&gt; Static pages are highly optimized for search engine indexing. The simplicity of static pages allows search engines to crawl and index the content more efficiently, leading to better search engine rankings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Improved Accessibility:&lt;/strong&gt; Basic HTML and CSS ensure compatibility across different devices and browsers, making the website accessible to a wider audience. This is particularly important for users with slower internet connections or older devices. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easier Maintenance:&lt;/strong&gt; With static pages, there is less code complexity, making maintenance and updates easier. Changes can be made quickly without the need to navigate through a complex framework structure.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Role of Front-End Frameworks in Modern Web Development
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Benefits of Front-End Frameworks
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojkxgl2s56zlj2mrmnhe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojkxgl2s56zlj2mrmnhe.png" alt="Angular, Vue, React" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite the concerns surrounding bloat, front-end frameworks offer several benefits that make them a popular choice among web developers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rapid Development:&lt;/strong&gt; Front-end frameworks provide pre-built components, libraries, and tools that streamline the development process. This allows developers to build complex web applications more efficiently and with fewer lines of code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code Organization:&lt;/strong&gt; Frameworks promote modular code structures, making it easier to manage and scale large projects. Components can be reused across different parts of the application, improving code maintainability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Developer Community:&lt;/strong&gt; Front-end frameworks have vibrant and active developer communities, providing access to extensive documentation, tutorials, and support. This can be invaluable for developers seeking assistance or looking to learn new techniques.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rich User Interfaces:&lt;/strong&gt; Front-end frameworks offer a range of UI components and styling options, enabling developers to create visually appealing and interactive user interfaces. This can enhance the overall user experience and engagement.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Assessing the Need for Front-End Frameworks
&lt;/h3&gt;

&lt;p&gt;While front-end frameworks offer numerous advantages, it is essential to assess the specific needs of a project before deciding to incorporate them. Consider the following factors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project Complexity:&lt;/strong&gt; If the project involves complex user interactions, real-time updates, or extensive data manipulation, a front-end framework may be beneficial. Frameworks provide the necessary tools and abstractions to handle these complexities efficiently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Development Speed:&lt;/strong&gt; Front-end frameworks excel in rapid development scenarios, where time-to-market is crucial. If the project requires quick iteration and deployment, a framework can expedite the development process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resource Constraints:&lt;/strong&gt; Consider the target audience and their devices. If the majority of users have limited resources, such as slow internet connections or older devices, opting for a lightweight approach without a framework might be more appropriate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Long-Term Maintenance:&lt;/strong&gt; Evaluate the long-term maintenance and scalability requirements of the project. Front-end frameworks can facilitate code organization and scalability, making it easier to maintain and extend the application over time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, the problem of bloat in web development is a growing concern, as web pages continue to increase in size and complexity. While front-end frameworks offer numerous benefits in terms of development speed and code organization, they can contribute to the bloat problem. However, static pages built with basic HTML and CSS provide a lightweight and efficient alternative, particularly for simpler projects or those with resource constraints.&lt;/p&gt;

&lt;p&gt;The decision to use a front-end framework should be based on a careful assessment of the project's needs, considering factors such as complexity, development speed, resource constraints, and long-term maintenance. By weighing the advantages and drawbacks of front-end frameworks, developers can make informed decisions and strike a balance between functionality and performance in their web applications.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
