<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Shanmukha Aditya]]></title><description><![CDATA[Writing about the things I build, the problems I encounter, and what I learn along the way.]]></description><link>https://shanmukha-aditya.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Shanmukha Aditya</title><link>https://shanmukha-aditya.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 20 Sep 2026 21:39:47 GMT</lastBuildDate><atom:link href="https://shanmukha-aditya.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why Accuracy Can Be Misleading in Failure Detection Systems]]></title><description><![CDATA[When evaluating a machine learning model, accuracy is usually one of the first metrics we look at.
It is simple, intuitive, and easy to compare. But that simplicity can become a problem when the data ]]></description><link>https://shanmukha-aditya.hashnode.dev/why-accuracy-can-be-misleading-in-failure-detection-systems</link><guid isPermaLink="true">https://shanmukha-aditya.hashnode.dev/why-accuracy-can-be-misleading-in-failure-detection-systems</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[anomaly detection]]></category><category><![CDATA[Model Evaluation]]></category><category><![CDATA[classification]]></category><category><![CDATA[Data Science]]></category><dc:creator><![CDATA[Munukutla Sri Shanmukha Aditya]]></dc:creator><pubDate>Fri, 04 Sep 2026 17:31:24 GMT</pubDate><content:encoded><![CDATA[<p>When evaluating a machine learning model, accuracy is usually one of the first metrics we look at.</p>
<p>It is simple, intuitive, and easy to compare. But that simplicity can become a problem when the data is highly imbalanced.</p>
<p><strong>Failure detection is a good example.</strong></p>
<p>Imagine a dataset with 10,000 observations:</p>
<ul>
<li><p>9,800 are normal</p>
</li>
<li><p>200 are failures</p>
</li>
</ul>
<p>Now consider a model that predicts every observation as normal.</p>
<p>It correctly classifies 9,800 observations and achieves <strong>98% accuracy</strong>.</p>
<p>But it detects <strong>none of the failures</strong>.</p>
<h2>Why Class Imbalance Matters</h2>
<p>In many real-world failure detection problems, failures are much less common than normal behavior.</p>
<p>A spacecraft doesn't fail every time telemetry is collected. A server doesn't crash on every request. A manufacturing machine doesn't produce defective components most of the time.</p>
<p>Normal behavior dominates the dataset.</p>
<p>This creates a class imbalance between:</p>
<p><strong>Normal observations → majority class</strong></p>
<p><strong>Failure/anomaly observations → minority class</strong></p>
<p>When one class vastly outnumbers the other, accuracy can be dominated by the majority class.</p>
<p>If 99% of the observations are normal, a model that predicts "normal" almost everywhere can achieve close to 99% accuracy without actually being good at detecting failures.</p>
<p>So the important question isn't only how many predictions the model gets right.</p>
<p>It is whether the model is <strong>actually detecting the minority class</strong>.</p>
<h2>What Accuracy Hides</h2>
<p>A model can make two important types of mistakes:</p>
<ul>
<li><p><strong>False positive:</strong> normal behavior is classified as a failure.</p>
</li>
<li><p><strong>False negative:</strong> an actual failure is classified as normal.</p>
</li>
</ul>
<p>These errors don't necessarily have the same consequences.</p>
<p>A false positive may result in an unnecessary alert or inspection. A false negative, however, means that an actual failure has gone undetected.</p>
<p>That difference matters when evaluating a failure detection system.</p>
<p>A model with slightly lower overall accuracy may therefore be more useful if it detects substantially more of the failures we care about.</p>
<h2>Looking Beyond Accuracy</h2>
<p>This is where <strong>precision and recall</strong> become important.</p>
<p><strong>Recall</strong> tells us how many of the actual failures the model detects.</p>
<p><strong>Precision</strong> tells us how often the failures identified by the model are actually failures.</p>
<p>Looking at both gives us a much better understanding of how the model behaves on the target class.</p>
<p>A model with high recall but very low precision may generate too many false alarms.</p>
<p>A model with high precision but low recall may identify failures accurately when it raises an alert, but miss too many failures altogether.</p>
<p>The right balance depends on the system and the consequences of these errors.</p>
<h2>A Practical Example</h2>
<p>I encountered this problem while working on supervised anomaly detection for NASA SMAP spacecraft telemetry during my research internship at IIT Patna.</p>
<p>The dataset had severe class imbalance, so accuracy wasn't enough when comparing the models. I evaluated approaches including One-Class SVM, Random Forest, and SVC, and optimized the final pipeline using RandomizedSearchCV and SMOTE.</p>
<p>The final model achieved <strong>88% accuracy</strong>, but that number alone didn't tell me how well it was detecting anomalies.</p>
<p>For the anomaly class, precision and recall were also important parts of the evaluation.</p>
<p>This was a useful reminder that the best-looking overall metric isn't necessarily the best indicator of whether a model is solving the problem it was built to solve.</p>
<h2>Accuracy Isn't Useless</h2>
<p>None of this means accuracy is a bad metric.</p>
<p>When the classes are reasonably balanced and different types of errors have similar consequences, accuracy can be a useful measure.</p>
<p>The problem is relying on it as the <strong>only measure of performance</strong> when the data is heavily imbalanced and the minority class is the primary concern.</p>
<p>Model evaluation should reflect the actual objective of the system.</p>
<p>For failure detection, that means asking not only whether the model is usually correct, but whether it can reliably identify the failures that matter.</p>
<h2>The Bigger Lesson</h2>
<p>A model can look successful according to a metric and still perform poorly at the task we built it for.</p>
<p>The metric isn't necessarily wrong. Sometimes, we're simply asking it to answer the wrong question.</p>
<p>So don't stop at the accuracy score. Ask:</p>
<blockquote>
<p><strong>Is the model actually good at detecting the failures?</strong></p>
</blockquote>
<p>That is what the system was built to do.</p>
]]></content:encoded></item><item><title><![CDATA[How I Evolved My URL Shortener with Redis]]></title><description><![CDATA[What is a URL shortener?
Let’s say you have a very long URL and want a shorter version of it so that it is easier to share or remember. How can you do that?
That’s where a URL shortener comes into the]]></description><link>https://shanmukha-aditya.hashnode.dev/how-i-evolved-my-url-shortener-with-redis</link><guid isPermaLink="true">https://shanmukha-aditya.hashnode.dev/how-i-evolved-my-url-shortener-with-redis</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[backend]]></category><category><![CDATA[Redis]]></category><category><![CDATA[System Design]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Munukutla Sri Shanmukha Aditya]]></dc:creator><pubDate>Sat, 15 Aug 2026 10:57:35 GMT</pubDate><content:encoded><![CDATA[<h2>What is a URL shortener?</h2>
<p>Let’s say you have a very long URL and want a shorter version of it so that it is easier to share or remember. How can you do that?</p>
<p>That’s where a URL shortener comes into the picture.</p>
<p>It takes a long URL and generates a short code by internally mapping that short code to the original URL. Whenever someone accesses the short code, the system looks up the original URL and redirects the user to it within a very short amount of time.</p>
<p>That’s it.</p>
<p>At least, that’s how I initially thought about it.</p>
<p>I wanted to build a simple URL shortener, so after a little planning, I started building <a href="https://github.com/m-s-shanmukha-aditya/routr-url-shortener">Routr</a>.</p>
<h2>Starting with the basic version</h2>
<p>I first created the URL model with the attributes I thought each URL needed: a unique ID, the original long URL, the short code mapped to it, and other relevant information.</p>
<p>Then I built the two basic operations:</p>
<ul>
<li><p>A <code>POST</code> endpoint to create a short URL.</p>
</li>
<li><p>A <code>GET</code> endpoint to redirect a short code to its original URL.</p>
</li>
</ul>
<p>For persistence, I used PostgreSQL to store and retrieve the URL mappings.</p>
<p>At this point, I had the basic URL shortener working.</p>
<p>But while building the initial prototype, I ran into my first interesting problem.</p>
<h2>How do I generate unique short codes?</h2>
<p>Every new URL needs its own short code. So how should I generate one?</p>
<p>I started experimenting with different approaches. One of the approaches I remember trying was hashing the long URL and using some part of the resulting hash as the short code.</p>
<p>The idea works, but there is a problem: collisions are possible. Two different URLs can theoretically produce the same hash or the same shortened portion of a hash.</p>
<p>I didn’t want to introduce collision detection and resolution just to generate a short code.</p>
<p>Eventually, I came across Base62 encoding.</p>
<p>Base62 uses lowercase letters, uppercase letters, and numbers to represent an integer in a compact form. This gave me a much simpler approach.</p>
<p>Instead of trying to generate a unique value from the URL itself, I could use something that was already guaranteed to be unique: the database-generated ID.</p>
<p>So the flow became:</p>
<p><strong>Database ID → Base62 encoding → Short code</strong></p>
<p>Since every row gets a unique ID from the database and Base62 is simply encoding that integer, different IDs produce different short codes.</p>
<p>That solved the short-code generation problem without having to deal with hash collisions.</p>
<h2>Then I added analytics</h2>
<p>After getting the basic functionality working, I thought: what if users could also see how many times a short URL had been accessed?</p>
<p>So I added click tracking.</p>
<p>I updated the URL model to store the click count and modified the redirect logic to update the count whenever someone accessed a short URL.</p>
<p>The feature worked.</p>
<p>But while testing it, I was also logging the requests being made, and I noticed something interesting.</p>
<p>Every redirect was causing a database read to retrieve the original URL and a database write to update the click count.</p>
<p>For one request, that is obviously not a problem.</p>
<p>But what happens if a large number of requests start hitting the same URL?</p>
<p>I started thinking: <strong>can I reduce the number of reads and writes going to my main database?</strong></p>
<p>That’s when Redis came into the picture.</p>
<h2>Introducing Redis</h2>
<p>I decided to use Redis as a cache in front of PostgreSQL.</p>
<p>The idea was simple: if a short URL has already been requested recently, why should I query PostgreSQL again for the same information?</p>
<p>So I started storing the mapping:</p>
<p><strong>short code → long URL</strong></p>
<p>in Redis.</p>
<p>Now, whenever a redirect request comes in, the application first checks Redis.</p>
<p>If the value is found, we have a cache hit:</p>
<p><strong>Redis → long URL → redirect</strong></p>
<p>No PostgreSQL lookup is required.</p>
<p>If the value isn’t found, we have a cache miss:</p>
<p><strong>Redis miss → PostgreSQL → get long URL → store in Redis → redirect</strong></p>
<p>The next request for the same short code can then be served from Redis.</p>
<p>This is essentially the cache-aside pattern.</p>
<h2>But what happens when the cache becomes full?</h2>
<p>Once Redis was working, another question came up.</p>
<p>If I keep putting URLs into the cache, won’t it eventually keep growing?</p>
<p>I obviously don’t want every URL that has ever been created to stay in Redis indefinitely.</p>
<p>So I introduced a TTL of one hour for cached URL mappings.</p>
<p>The idea was that cached entries should not remain there forever. After the TTL expires, the entry is removed, and if somebody requests that URL again later, it can be fetched from PostgreSQL and cached again.</p>
<p>This gave me a simple way to prevent cached entries from accumulating indefinitely.</p>
<h2>But analytics had another problem</h2>
<p>I had solved the repeated database reads, but there was still the analytics problem.</p>
<p>Every redirect was still incrementing the click count.</p>
<p>I started thinking: do I really need to write to PostgreSQL after every single click?</p>
<p>Probably not.</p>
<p>Instead, I moved the live click counter into Redis as well.</p>
<p>Now every redirect increments the counter in Redis rather than immediately updating PostgreSQL.</p>
<p>Then I added a scheduled synchronization process that periodically takes the accumulated Redis count and adds it to the persisted count in PostgreSQL.</p>
<p>For example, if PostgreSQL has:</p>
<p><strong>1,000 clicks</strong></p>
<p>and Redis currently has:</p>
<p><strong>250 clicks</strong></p>
<p>the synchronization updates PostgreSQL to:</p>
<p><strong>1,250 clicks</strong></p>
<p>and resets the Redis counter back to zero.</p>
<p>There was one more thing to consider.</p>
<p>What if someone asks for the analytics before the next synchronization happens?</p>
<p>In that case, PostgreSQL might say 1,000 while Redis has another 250 live clicks.</p>
<p>So when returning analytics, I combine the persisted PostgreSQL count with the current Redis count to give the latest available number.</p>
<h2>The final architecture</h2>
<p>So what started as a simple URL shortener gradually evolved into something like:</p>
<p><strong>PostgreSQL</strong> handles persistent data.</p>
<p><strong>Redis</strong> handles frequently accessed URL mappings and live click counters.</p>
<p><strong>Cache-aside</strong> keeps repeated URL lookups away from PostgreSQL.</p>
<p><strong>TTL</strong> prevents cached URLs from remaining indefinitely.</p>
<p><strong>Scheduled synchronization</strong> moves accumulated analytics from Redis back into PostgreSQL.</p>
<p>The interesting part for me wasn’t implementing Redis itself.</p>
<p>It was seeing how one decision created another problem, which then led to another architectural decision.</p>
<p>I started with:</p>
<p><strong>URL shortener → PostgreSQL</strong></p>
<p>Then:</p>
<p><strong>Need unique short codes → Base62 + database ID</strong></p>
<p>Then:</p>
<p><strong>Analytics → database writes on every click</strong></p>
<p>Then:</p>
<p><strong>Repeated database access → Redis cache</strong></p>
<p>Then:</p>
<p><strong>Cache growth → TTL</strong></p>
<p>Then:</p>
<p><strong>Too many analytics writes → Redis counters + periodic synchronization</strong></p>
<p>And that’s probably the biggest thing I learned from building Routr.</p>
<p>I don’t think a system needs to start with a complicated architecture just because it might eventually have a lot of traffic. Start with something simple, understand how it behaves, identify the actual problems, and then introduce complexity when there is a reason for it.</p>
<p>I haven’t benchmarked Routr yet, so I can’t claim a specific percentage reduction in latency or database load. That’s something I want to do next.</p>
<p>For now, the project gave me something more valuable: a better understanding of how backend architecture evolves as you start asking, <strong>“What happens if this gets bigger?”</strong></p>
<p>Try <a href="https://routrr.vercel.app/"><strong>Routr →</strong></a></p>
]]></content:encoded></item></channel></rss>