<?xml version="1.0" encoding="UTF-8" ?>
    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
      <channel>
        <title>Ratn Labs</title>
        <description>Systems thinking, backend architecture, and AI engineering. Building scalable systems and sharing technical insights.</description>
        <link>https://blog.ratnesh-maurya.com</link>
        <language>en-us</language>
        <managingEditor>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</managingEditor>
        <webMaster>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</webMaster>
        <lastBuildDate>Sun, 10 May 2026 19:34:22 GMT</lastBuildDate>
        <atom:link href="https://blog.ratnesh-maurya.com/rss.xml" rel="self" type="application/rss+xml"/>
        
      <item>
        <title><![CDATA[That Little Square Does a Lot: How QR Codes Actually Work]]></title>
        <description><![CDATA[From the black-and-white squares on your pizza box to the split-second bank transfer — here's how QR codes and UPI payments actually work under the hood, explained without the jargon.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/QR-Codes-The-Invisible-Magic-Behind-Every-Scan-and-Pay.jpg" alt="That Little Square Does a Lot: How QR Codes Actually Work" /></p><h1 id="that-little-square-does-a-lot-how-qr-codes-actually-work">That Little Square Does a Lot: How QR Codes Actually Work</h1>
<p>You've scanned hundreds of them — restaurant menus, bus tickets, payment counters, event passes, Instagram profiles.</p>
<p>You point your phone. Wait half a second. Something happens.</p>
<p>But what <em>is</em> that little square, really? And how does pointing a camera at a printed sticker somehow move money between two bank accounts in under a second?</p>
<p>It turns out this is one of the most elegant pieces of everyday engineering. Once you see how it works, you can't unsee it.</p>
<hr>
<blockquote>
<p><strong>TL;DR</strong></p>
<p>A QR code is just text — encoded as a grid of black-and-white squares. Your camera decodes that grid back into text using math. For payments, that text is a special address that tells your UPI app who to pay and how much. Then five institutions — your bank, NPCI, the merchant's bank, and a couple of others — silently coordinate a real-time transfer in milliseconds. The Soundbox speaks because it keeps a permanent cloud connection open, waiting for exactly that moment.</p>
</blockquote>
<hr>
<h2 id="chapter-1-what-even-is-a-qr-code">Chapter 1: What Even Is a QR Code?</h2>
<p>Let's start with the simplest possible explanation.</p>
<p>A QR code is just <strong>text, printed as a picture</strong>.</p>
<p>That's it. Everything else is engineering detail on top of that single idea.</p>
<p>Instead of writing <code>upi://pay?pa=chaiwala@upi&#x26;am=50</code>, someone converts that string into a grid of black and white squares. When you point a camera at that grid, the phone runs some math and gets the text back out.</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/1/1d/QR_Code_Structure_Example_3.svg" alt="QR code anatomy — finder patterns, timing patterns, data zones">
<em>Every zone has a job: corner squares for orientation, tiny dots for grid calibration, everything else is data. Source: <a href="https://commons.wikimedia.org/wiki/File:QR_Code_Structure_Example_3.svg">Wikimedia Commons</a> (CC BY-SA 3.0)</em></p>
<h3 id="why-squares-why-not-just-a-regular-barcode">Why squares? Why not just a regular barcode?</h3>
<p>A regular barcode — the zebra-striped one on a cereal box — only stores data in <strong>one direction</strong>, horizontally. It's like a single line of text. That limits it to maybe 20-30 numbers.</p>
<p>A QR code uses <strong>both dimensions</strong>. Horizontal <em>and</em> vertical. Think of it as a page of text versus a single line. Same physical space, but now you can pack in 4,000+ characters.</p>
<p>This is what made QR codes revolutionary when Denso Wave invented them in 1994 — originally just to track car parts in a Toyota factory. Nobody imagined they'd end up on every restaurant table on Earth.</p>
<hr>
<h2 id="chapter-2-the-anatomy-what-are-all-those-squares-for">Chapter 2: The Anatomy — What Are All Those Squares For?</h2>
<p>Look closely at any QR code. You'll notice it's not random noise. It has a very specific structure.</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/a/a5/QR_Code_Structure_Example_2.svg" alt="QR code structure diagram showing all functional zones labelled">
<em>It's not random noise — every region is precisely defined by the ISO standard. Source: <a href="https://commons.wikimedia.org/wiki/File:QR_Code_Structure_Example_2.svg">Wikimedia Commons</a> (CC BY-SA 3.0)</em></p>
<h3 id="the-three-big-squares-in-the-corners">The three big squares in the corners</h3>
<p>Those three bold nested squares — top-left, top-right, bottom-left — are not data. They're <strong>navigation anchors</strong>.</p>
<p>Your phone's camera finds these three squares <em>first</em>, before reading anything else. Once it locates them, it instantly knows:</p>
<ul>
<li>Where the QR code starts and ends</li>
<li>What angle it's tilted at</li>
<li>How large each data "cell" is</li>
<li>Whether the image is distorted or curved</li>
</ul>
<p>This is why you can scan a QR code:</p>
<ul>
<li>Sideways</li>
<li>At an angle</li>
<li>Upside down</li>
<li>Printed on a curved bottle</li>
<li>Slightly crumpled</li>
</ul>
<p>The three anchor squares let the software mathematically "flatten" the image before reading the data cells.</p>
<h3 id="the-tiny-dots-in-the-middle-the-data">The tiny dots in the middle — the data</h3>
<p>Everything between and around the anchors is actual data: the encoded text, plus extra redundancy bits for error correction.</p>
<h3 id="the-quiet-zone">The quiet zone</h3>
<p>Notice how QR codes always have a white border around them? That blank space isn't wasted — it's mandatory. It tells the scanner "the code starts here." Without it, the camera can't distinguish the code from whatever is printed around it.</p>
<hr>
<h2 id="chapter-3-what-happens-when-you-scan">Chapter 3: What Happens When You Scan?</h2>
<p>Here's the sequence that happens the moment you point your camera at a QR code:</p>
<p><strong>1. The camera captures a frame and converts it to black-and-white.</strong>
No colour needed. Just dark and light.</p>
<p><strong>2. Software scans for the three corner anchors.</strong>
It's looking for the specific pattern of that nested square. Once it finds three of them, it locks on.</p>
<p><strong>3. It maps the grid.</strong>
Using the anchor positions and the thin "timing strips" between them (those alternating rows of dots), the software figures out where every data cell is and reads each one as a 0 or 1.</p>
<p><strong>4. It reverses a mathematical mask.</strong>
During QR code creation, the data gets scrambled with an XOR mask to prevent large uniform patches of black or white (which confuse cameras). The scanner reverses this.</p>
<p><strong>5. Error correction runs.</strong>
If a few cells were smudged or misread, a Reed-Solomon algorithm reconstructs the original data. This is the same math NASA used for deep-space probes and CDs use for skipping prevention.</p>
<p><strong>6. Output: plain text.</strong>
The whole thing — from camera frame to decoded string — takes milliseconds.</p>
<hr>
<h2 id="chapter-4-the-error-correction-magic">Chapter 4: The Error Correction Magic</h2>
<p>This part deserves its own section because it's genuinely remarkable.</p>
<p>A QR code can be <strong>physically damaged</strong> — torn, smudged, covered by a logo — and still decode perfectly.</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/d/d0/QR_code_for_mobile_English_Wikipedia.svg" alt="Wikipedia&#x27;s QR code with its logo embedded in the center">
<em>Wikipedia's own QR code has its logo sitting right in the middle — covering real data cells. It still scans perfectly because error correction fills in what's missing. Source: <a href="https://commons.wikimedia.org/wiki/File:QR_code_for_mobile_English_Wikipedia.svg">Wikimedia Commons</a> (public domain)</em></p>
<p>There are four levels of error correction you can choose when generating a QR code:</p>
<table>
<thead>
<tr>
<th>Level</th>
<th>Survives this much damage</th>
</tr>
</thead>
<tbody>
<tr>
<td>L (Low)</td>
<td>~7%</td>
</tr>
<tr>
<td>M (Medium)</td>
<td>~15%</td>
</tr>
<tr>
<td>Q (Quartile)</td>
<td>~25%</td>
</tr>
<tr>
<td>H (High)</td>
<td>~30%</td>
</tr>
</tbody>
</table>
<p>Payment QR codes often use level H — so even if 30% of the squares are destroyed, the data is fully recoverable.</p>
<p>The math behind this (Reed-Solomon codes) operates on something called a Galois Field — a finite number system where all arithmetic "wraps around." It's the same fundamental idea used in RAID storage arrays and satellite communications. The QR code on your chai stall's counter uses mathematics built for outer space.</p>
<hr>
<h2 id="chapter-5-static-vs-dynamic-two-very-different-beasts">Chapter 5: Static vs Dynamic — Two Very Different Beasts</h2>
<p>Not all payment QR codes work the same way. There are two fundamentally different types.</p>
<h3 id="the-printed-sticker-on-the-counter-static-qr">The printed sticker on the counter — Static QR</h3>
<p>That laminated card at your local vegetable vendor? That's a static QR code.</p>
<p>It was generated <strong>once</strong> during merchant onboarding, printed, and it never changes. It only stores the merchant's UPI address — something like <code>vendor@oksbi</code>. When you scan it, your UPI app opens and asks you to <strong>type the amount yourself</strong>.</p>
<p><strong>The good:</strong> It's cheap to produce, requires no internet connection to display, and lasts indefinitely.</p>
<p><strong>The problem:</strong> You type the amount. You might type ₹100 when you owe ₹1,000. The merchant has no way to pre-confirm the correct amount. Reconciling which payment matched which order is a manual headache.</p>
<h3 id="the-screen-at-a-restaurant-dynamic-qr">The screen at a restaurant — Dynamic QR</h3>
<p>At a modern restaurant, grocery store, or any POS system, the QR code is generated <strong>fresh for every single transaction</strong>.</p>
<p>You scan it and your UPI app already shows: "Pay ₹847 to Domino's Koramangala." Pre-filled. No manual entry. One fingerprint, PIN, done.</p>
<p>This QR is generated live by the payment aggregator's backend (PhonePe Business, Razorpay, Paytm for Business, etc.) and encodes:</p>
<ul>
<li>Merchant's UPI address</li>
<li>Exact transaction amount</li>
<li>A unique order reference ID</li>
<li>An expiry timestamp (usually 10-15 minutes)</li>
</ul>
<p>Scan a day-old dynamic QR code and it rejects. This is intentional — expired codes can't be replayed or reused by anyone.</p>
<hr>
<h2 id="chapter-6-the-payment-journey-half-a-second-five-institutions">Chapter 6: The Payment Journey — Half a Second, Five Institutions</h2>
<p>Here's the part most people never think about: what happens <em>after</em> you tap "Pay."</p>
<p><img src="https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=900&#x26;auto=format&#x26;fit=crop" alt="A person paying at a counter using a smartphone, merchant behind the counter">
<em>From this moment to the Soundbox speaking — five organisations talk to each other in under a second.</em></p>
<h3 id="the-chain-of-events">The chain of events</h3>
<p><strong>Your phone reads the QR code and finds this text:</strong></p>
<pre><code>upi://pay?pa=merchant@bank&#x26;pn=Store Name&#x26;am=150.00&#x26;tr=ORD123456
</code></pre>
<p>Your phone sees <code>upi://</code> and immediately knows — same way <code>mailto:</code> opens your email app — to launch your UPI payment app. This is called <strong>deep linking</strong>.</p>
<hr>
<p><strong>You enter your PIN. But it never travels in plain text.</strong></p>
<p>Every UPI app — Google Pay, PhonePe, Paytm — embeds a locked-down cryptographic module called the <strong>NPCI Common Library</strong>. It's a piece of isolated code that every UPI app is legally required to include.</p>
<p>When you type your PIN, it goes <em>into</em> this vault:</p>
<ul>
<li>Mixed with device-specific values</li>
<li>Hashed (converted into an irreversible fingerprint)</li>
<li>Wrapped in multiple layers of encryption</li>
</ul>
<p>Think of it this way: your PIN gets locked inside a box. That box gets locked inside another box. The second box's key is held by your bank — and only your bank.</p>
<p>PhonePe never sees your PIN. NPCI never sees your PIN. Nobody in the middle can.</p>
<hr>
<p><strong>The encrypted package travels a fixed route:</strong></p>
<pre><code>Your phone
  → PhonePe's servers
    → Your bank (the Payer PSP)
      → NPCI UPI Switch
        → Merchant's bank (the Payee PSP)
</code></pre>
<p>Each hop verifies the cryptographic integrity before passing it along. Nobody can tamper with the amount or the recipient mid-flight.</p>
<hr>
<p><strong>NPCI is the air traffic controller.</strong></p>
<p>The NPCI Switch sits at the center. It:</p>
<ul>
<li>Decrypts the outer routing layer</li>
<li>Looks up what bank account <code>merchant@bank</code> belongs to</li>
<li>Forwards the PIN payload <em>only</em> to your bank (which holds the only key)</li>
<li>Waits for your bank to confirm the debit</li>
<li>Tells the merchant's bank to credit the account</li>
<li>Broadcasts SUCCESS to everyone</li>
</ul>
<p>Your bank is the only entity that can decrypt your PIN and verify it. If it matches and balance is sufficient, the debit happens. Signal flows back up the chain.</p>
<p><strong>Total time: typically 200–800 milliseconds.</strong></p>
<hr>
<h2 id="chapter-7-why-does-the-soundbox-speak-instantly">Chapter 7: Why Does the Soundbox Speak Instantly?</h2>
<p>This is the detail that surprises most people.</p>
<p>The Soundbox at the counter isn't checking its inbox every second. It isn't waiting for an SMS. It maintains a <strong>permanent open connection</strong> to the payment aggregator's cloud servers — like a phone call that's always on hold, never hanging up.</p>
<p>The protocol used for this is called <strong>MQTT</strong> (Message Queuing Telemetry Transport) — originally designed for oil pipeline sensors in remote locations where bandwidth is expensive. It's extremely lightweight, works over 2G/3G, and keeps the connection alive with tiny "are you there?" heartbeat packets.</p>
<p>The moment NPCI confirms the credit:</p>
<ol>
<li>PhonePe's backend fires a notification to the Soundbox's cloud channel</li>
<li>MQTT delivers it in milliseconds over the persistent connection</li>
<li>The Soundbox lights its green LED</li>
<li>It plays audio assembled from pre-stored clips:</li>
</ol>
<pre><code>[jingle] + ["ek sau pachaas"] + ["rupaye"] + ["praapt hue"]
</code></pre>
<p>That slightly robotic quality in the voice? That's because it's stitching together pre-recorded number clips, not synthesising speech from scratch.</p>
<hr>
<h2 id="chapter-8-what-stops-someone-from-making-a-fake-qr-code">Chapter 8: What Stops Someone from Making a Fake QR Code?</h2>
<p>This is a real attack. It's called <strong>"QR code overlay fraud"</strong> — someone prints a QR code pointing to their own account and pastes it over the merchant's legitimate code.</p>
<p>You scan it. Looks identical. Money goes to the wrong person.</p>
<p><img src="https://images.unsplash.com/photo-1563013544-824ae1b704d3?w=900&#x26;auto=format&#x26;fit=crop" alt="A QR code payment standee at a merchant counter">
<em>That sticker on the counter could, in theory, be replaced by a fraudster's code. Cryptographic signatures make this attack detectable.</em></p>
<p>To combat this, UPI payment QR codes include a <strong>cryptographic signature</strong> — a mathematical fingerprint generated using the merchant's private encryption key.</p>
<p>When your UPI app scans a payment code:</p>
<ol>
<li>It reads the full URL</li>
<li>It verifies the signature against NPCI's public key registry</li>
<li>If the signature doesn't match — even one character was changed — the transaction is blocked immediately</li>
</ol>
<p>A fraudster can copy a QR code and print it. But they cannot fake the cryptographic signature without the merchant's private key. The math makes forgery detectable.</p>
<hr>
<h2 id="the-bigger-picture">The Bigger Picture</h2>
<p>What began as a Toyota factory tool for tracking car components in 1994 is now the physical-to-digital gateway for one of the world's largest payment networks — handling half a billion users and billions of monthly transactions.</p>
<p>Every time you scan and pay, you're triggering:</p>
<ul>
<li>A camera doing real-time computer vision</li>
<li>Reed-Solomon error correction reconstructing possibly-damaged data</li>
<li>Deep linking routing your OS to the right app</li>
<li>A cryptographic vault protecting your PIN from everyone, including the app itself</li>
<li>Five institutions coordinating an inter-bank transfer in milliseconds</li>
<li>An IoT device playing spliced audio over a connection that never closes</li>
</ul>
<p>All in under a second. From a little black-and-white square on a laminated card.</p>
<hr>
<h2 id="further-reading">Further Reading</h2>
<p>If this sparked curiosity, here's where to go next:</p>
<p><strong>On QR Codes:</strong></p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/QR_code">QR Code — Wikipedia</a> — The full history, Denso Wave origins, and technical evolution</li>
<li><a href="https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction">Reed–Solomon error correction — Wikipedia</a> — The math that makes smudged codes readable</li>
<li><a href="https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders">Reed–Solomon codes for coders — Wikiversity</a> — Hands-on if you want to implement it</li>
<li><a href="https://blog.ansi.org/ansi/iso-iec-18004-2024-qr-code-bar-code-symbology/">ISO/IEC 18004 standard overview — ANSI Blog</a> — The formal spec in readable form</li>
</ul>
<p><strong>On UPI &#x26; Payments:</strong></p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Unified_Payments_Interface">Unified Payments Interface — Wikipedia</a> — The full architecture and history</li>
<li><a href="https://paytm.com/blog/payments/upi/the-technology-stack-how-upi-powers-your-scan-pay-transactions/">How UPI powers Scan &#x26; Pay — Paytm Blog</a> — The four-party model explained</li>
<li><a href="https://www.usenix.org/system/files/sec20summer_kumar_prepub.pdf">Security Analysis of UPI and Payment Apps in India — USENIX</a> — Academic deep dive into UPI security</li>
<li><a href="https://razorpay.com/docs/build/browser/assets/images/upi-common-library-spec-android-17.pdf">UPI Common Library Specification — Razorpay Docs</a> — How the PIN encryption actually works</li>
</ul>
<p><strong>On the IoT side:</strong></p>
<ul>
<li><a href="https://eazypaytech.com/payment-soundbox-works/">How Payment Soundbox Works — EazyPay Tech</a> — Full breakdown of the Soundbox hardware and pipeline</li>
<li><a href="https://mqtt.org/">MQTT Protocol</a> — The lightweight protocol that connects Soundboxes to the cloud</li>
</ul>
<p><strong>Go deeper into system design:</strong></p>
<ul>
<li><a href="https://blog.codekerdos.in/the-system-design-behind-phonepes-upi-engine-what-every-developer-should-know/">The System Design Behind PhonePe's UPI Engine</a> — Database sharding, consistent hashing, Raft consensus</li>
</ul>
<hr>
<p><em>The next time the Soundbox speaks, you'll know exactly what just happened — and how many pieces had to work perfectly for that half-second to feel effortless.</em></p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/QR-Codes-The-Invisible-Magic-Behind-Every-Scan-and-Pay</link>
        <guid>https://blog.ratnesh-maurya.com/blog/QR-Codes-The-Invisible-Magic-Behind-Every-Scan-and-Pay</guid>
        <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>System Design</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/QR-Codes-The-Invisible-Magic-Behind-Every-Scan-and-Pay.jpg" length="92346" type="image/jpeg" />
      </item>
      <item>
        <title><![CDATA[Go 1.26.2: Security Fixes, Regression Patches, Upgrade Playbook]]></title>
        <description><![CDATA[A beginner-friendly and technical deep dive into Go 1.26.2: what changed, why it matters, and how to upgrade safely in production.]]></description>
        <content:encoded><![CDATA[<h1>Go 1.26.2 Released: Security Fixes, Regression Patches, and an Upgrade Playbook</h1>
<p>Go 1.26.2 was released on <strong>2026-04-07</strong> as a patch release focused on security and stability.</p>
<p>If you are running Go 1.26.x in production, this is the kind of release you should evaluate quickly.</p>
<p>Go 1.26.2 includes security fixes to the <code>go</code> command, compiler, and the <code>archive/tar</code>, <code>crypto/tls</code>, <code>crypto/x509</code>, <code>html/template</code>, and <code>os</code> packages, plus bug fixes across the <code>go</code> command, <code>go fix</code>, compiler, linker, runtime, <code>net</code>, <code>net/http</code>, and <code>net/url</code>.</p>
<p><img src="https://blog.ratnesh-maurya.com/images/blog/go-1-26-2-release-overview.svg" alt="Go 1.26.2 release at a glance"></p>
<h2>TL;DR (beginner + technical)</h2>
<ul>
<li>If you are on <strong>1.26.0</strong> or <strong>1.26.1</strong>, plan an upgrade to <strong>1.26.2</strong>.</li>
<li>If your service is internet-facing, treat this as high priority because security-sensitive packages were patched.</li>
<li>Verify checksums before installation.</li>
<li>Roll out with canary + SLO guardrails, not a full one-shot deployment.</li>
</ul>
<h2>If you are new: what does "1.26.2" mean?</h2>
<p>Go versions follow a <code>major.minor.patch</code> pattern:</p>
<ul>
<li><code>1</code> = major line</li>
<li><code>26</code> = minor release line</li>
<li><code>2</code> = patch release</li>
</ul>
<p>Patch releases are usually about <strong>fixing behavior</strong>, not adding new language features.</p>
<h2>Why this patch matters technically</h2>
<p>Go 1.26 introduced important runtime and toolchain changes. Point releases like 1.26.2 are where real-world regressions and security backports get addressed.</p>
<p>Use this release as both a security update and a reliability update.</p>
<table>
<thead>
<tr>
<th>Area</th>
<th>Patch focus in 1.26.2</th>
<th>Practical risk if delayed</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>crypto/tls</code>, <code>crypto/x509</code></td>
<td>Security hardening and correctness fixes</td>
<td>TLS/cert path issues at trust boundaries</td>
</tr>
<tr>
<td><code>html/template</code></td>
<td>Security-related fixes</td>
<td>Potential template safety exposure</td>
</tr>
<tr>
<td><code>cmd/go</code>, <code>go fix</code></td>
<td>Toolchain reliability fixes</td>
<td>Slow/hanging CI or broken automation flows</td>
</tr>
<tr>
<td>Compiler/linker/runtime</td>
<td>Regression and crash fixes</td>
<td>Build or runtime instability in production</td>
</tr>
<tr>
<td><code>net</code>, <code>net/http</code>, <code>net/url</code></td>
<td>Networking correctness fixes</td>
<td>Subtle request/routing/parsing behavior regressions</td>
</tr>
</tbody>
</table>
<h2>Deep look at the Go1.26.2 milestone</h2>
<p>Using the public GitHub milestone API for <code>Go1.26.2</code>:</p>
<ul>
<li>Milestone state: <code>closed</code></li>
<li>Open issues: <code>0</code></li>
<li>Closed issues: <code>31</code></li>
</ul>
<h3>What the issue distribution tells us</h3>
<p>From a title/label scan of closed issues, the cluster is practical and production-facing:</p>
<ul>
<li>CVE-related titles: <code>10</code></li>
<li>Security backport-labeled titles: <code>8</code></li>
<li>Compiler/runtime-labeled items: <code>10</code></li>
<li><code>cmd/go</code>-labeled items: <code>1</code></li>
<li>Testing-labeled items: <code>2</code></li>
<li>Documentation/backport consistency items: <code>4</code></li>
</ul>
<p>This is the normal fingerprint of a mature patch release: security + high-impact regressions + release-quality cleanup.</p>
<h3>Security and CVE signal</h3>
<p>The milestone shows clear CVE and security backport activity, including:</p>
<ul>
<li><a href="https://github.com/golang/go/issues/78428">#78428</a> <code>security: fix CVE-2026-32283 [1.26 backport]</code></li>
<li><a href="https://github.com/golang/go/issues/78426">#78426</a> <code>security: fix CVE-2026-32282 [1.26 backport]</code></li>
<li><a href="https://github.com/golang/go/issues/78424">#78424</a> <code>security: fix CVE-2026-27144 [1.26 backport]</code></li>
<li><a href="https://github.com/golang/go/issues/78422">#78422</a> <code>security: fix CVE-2026-27140 [1.26 backport]</code></li>
<li><a href="https://github.com/golang/go/issues/78362">#78362</a> <code>crypto/x509 ... (CVE-2026-32280)</code></li>
<li><a href="https://github.com/golang/go/issues/78360">#78360</a> <code>crypto/x509 ... (CVE-2026-32281)</code></li>
</ul>
<p>Even without changing your app code, patching core trust-boundary packages (<code>tls</code>, <code>x509</code>, templates, and toolchain) reduces real production risk.</p>
<h3>Regression fixes you may actually notice</h3>
<p>Representative examples from the milestone:</p>
<ul>
<li><a href="https://github.com/golang/go/issues/78058">#78058</a>: <code>cmd/go</code> cache trim could block for 20+ minutes on macOS.</li>
<li><a href="https://github.com/golang/go/issues/78111">#78111</a>: <code>net/url</code> parsing regression affecting MongoDB multi-host connection strings.</li>
<li><a href="https://github.com/golang/go/issues/78041">#78041</a>: runtime crash on Windows in 1.26.0/1.26.1.</li>
<li><a href="https://github.com/golang/go/issues/78239">#78239</a>: linker panic on darwin/arm64.</li>
<li><a href="https://github.com/golang/go/issues/78191">#78191</a>: <code>cmd/fix</code> panic in edge cases.</li>
</ul>
<p>Beginner translation: these are exactly the kinds of bugs that become random CI failures, odd runtime crashes, or flaky production behavior.</p>
<p><img src="https://blog.ratnesh-maurya.com/images/blog/go-1-26-2-milestone-breakdown.svg" alt="Go 1.26.2 milestone breakdown"></p>
<h2>Download artifacts and checksum verification</h2>
<p>Official download page: <a href="https://go.dev/dl/">go.dev/dl</a></p>
<p>Selected Go 1.26.2 artifacts from the official download feed:</p>
<table>
<thead>
<tr>
<th>Artifact</th>
<th>Platform</th>
<th>Size</th>
<th>SHA256</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>go1.26.2.src.tar.gz</code></td>
<td>Source</td>
<td>33 MB</td>
<td><code>2e91ebb6947a96e9436fb2b3926a8802efe63a6d375dffec4f82aa9dbd6fd43b</code></td>
</tr>
<tr>
<td><code>go1.26.2.linux-amd64.tar.gz</code></td>
<td>Linux x86-64</td>
<td>64 MB</td>
<td><code>990e6b4bbba816dc3ee129eaeaf4b42f17c2800b88a2166c265ac1a200262282</code></td>
</tr>
<tr>
<td><code>go1.26.2.darwin-arm64.pkg</code></td>
<td>macOS Apple Silicon</td>
<td>63 MB</td>
<td><code>5daa0b7ba59f703c5b6be2bd48437062224fd9244160e8e73a1c9f7eb8a11784</code></td>
</tr>
<tr>
<td><code>go1.26.2.darwin-amd64.pkg</code></td>
<td>macOS Intel</td>
<td>66 MB</td>
<td><code>5eab5ad8943e7666554fddd72ecbcbe64cf8f04197d6e06486fbb395b779fd8d</code></td>
</tr>
<tr>
<td><code>go1.26.2.windows-amd64.msi</code></td>
<td>Windows x86-64</td>
<td>59 MB</td>
<td><code>84826eca833548bb2beabe7429052eaaec18faa902fde723898d906b42e59a73</code></td>
</tr>
</tbody>
</table>
<p>Why checksum verification matters: it confirms the artifact you downloaded is exactly what Go published.</p>
<p>Example verification (macOS/Linux shell):</p>
<pre><code class="language-bash">curl -LO https://go.dev/dl/go1.26.2.linux-amd64.tar.gz
shasum -a 256 go1.26.2.linux-amd64.tar.gz
# expected: 990e6b4bbba816dc3ee129eaeaf4b42f17c2800b88a2166c265ac1a200262282
</code></pre>
<h2>Production-safe upgrade playbook</h2>
<p>If your team has no formal release process yet, just follow the steps in order and keep a rollback version ready.</p>
<pre><code>Use Go 1.26.2 explicitly in CI images, local dev environments, and build containers. Avoid implicit latest tags.

```dockerfile
FROM golang:1.26.2-alpine
```


Validate what your runner is actually using before tests.

```bash
go version
go env GOTOOLCHAIN GOOS GOARCH
```


Start with your existing suite, then add race detection for services that handle concurrency heavily.

```bash
go test ./...
go test ./... -race
go vet ./...
```


Execute `govulncheck` and compare findings against your previous baseline.

```bash
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
```


Add focused tests for URL parsing, HTTP handling, TLS handshakes, and cert verification in critical flows.


Canary first, then staged rollout with explicit SLO gates (error rate, latency, crash loops, build stability).


Preserve the previous working toolchain image/tag so rollback is a fast switch, not a rebuild.
</code></pre>
<p><img src="https://blog.ratnesh-maurya.com/images/blog/go-1-26-2-upgrade-flow.svg" alt="Go 1.26.2 upgrade flow"></p>
<h2>Copy-paste CI gate (practical baseline)</h2>
<pre><code class="language-bash">set -euo pipefail

go version
go env GOTOOLCHAIN GOOS GOARCH

go test ./...
go test ./... -race
go vet ./...

govulncheck ./...
</code></pre>
<h2>Should you upgrade now?</h2>
<ul>
<li><strong>Upgrade now (high priority):</strong> internet-facing services, heavy TLS/cert usage, or strict security posture.</li>
<li><strong>Upgrade soon (scheduled):</strong> internal services on 1.26.0/1.26.1.</li>
<li><strong>Plan migration path:</strong> older major line users that need broader compatibility testing.</li>
</ul>
<h2>Read more</h2>
<p>The canonical summary of packages touched in 1.26.2.</p>
<p>Understand what 1.26 introduced so you can interpret which 1.26.2 fixes are stabilization backports.</p>
<p>Official binaries, source tarball, and checksums for every platform.</p>
<p>Closed issue set showing exactly what was backported into the patch.</p>
<p>Guidance on govulncheck, vulnerability database, and release security policy.</p>
<h2>Data provenance</h2>
<p>This article and its visuals are based on:</p>
<ol>
<li>Official Go release notes and release history.</li>
<li>Official Go download index and checksums.</li>
<li>Public GitHub milestone API data for <code>Go1.26.2</code>.</li>
</ol>
<p>The embedded images in this post are custom summary visuals created from those public sources.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/Go-1-26-2-Released-Security-Fixes-Regression-Patches-and-Upgrade-Playbook</link>
        <guid>https://blog.ratnesh-maurya.com/blog/Go-1-26-2-Released-Security-Fixes-Regression-Patches-and-Upgrade-Playbook</guid>
        <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>Golang</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/go-1-26-2-release-overview.svg" length="2534" type="image/svg+xml" />
      </item>
      <item>
        <title><![CDATA[cursor-claude-personas: AI Domain Expert Personas in 30 Seconds]]></title>
        <description><![CDATA[A free, open-source collection of 38 role-based AI persona packs for Claude Code, Cursor, and VS Code. Copy one folder into your project and your AI acts like a seasoned specialist.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/cursor-claude-personas.png" alt="cursor-claude-personas: AI Domain Expert Personas in 30 Seconds" /></p><h1 id="cursor-claude-personas-give-your-ai-coding-assistant-a-domain-expert-brain-in-30-seconds">cursor-claude-personas: Give Your AI Coding Assistant a Domain Expert Brain in 30 Seconds</h1>
<blockquote>
<p><strong>TL;DR</strong> — <code>cursor-claude-personas</code> is a free, open-source collection of 38 role-based AI persona packs for Claude Code, Cursor, and VS Code. Copy one folder into your project, reopen your editor, and your AI assistant instantly behaves like a seasoned specialist — whether that's a senior Python developer, a DevOps engineer, a security expert, or a UI/UX designer.</p>
</blockquote>
<hr>
<h2 id="the-problem-generic-ai-gives-generic-code">The Problem: Generic AI Gives Generic Code</h2>
<p>You have Claude Code or Cursor open. You ask it to add an endpoint. It writes something that works — but it doesn't know your preferred framework patterns, it doesn't follow your domain conventions, and it certainly doesn't remember that you care deeply about SQL index hygiene or GraphQL schema design.</p>
<p>The usual fix is a long system prompt you paste in every time. Or a <code>.cursorrules</code> file you spend hours writing. Or endless back-and-forth corrections.</p>
<p><strong>cursor-claude-personas solves this with a dead-simple copy-paste.</strong></p>
<hr>
<h2 id="what-is-cursor-claude-personas">What Is cursor-claude-personas?</h2>
<p><a href="https://github.com/ratnesh-maurya/cursor-claude-personas">cursor-claude-personas</a> is a GitHub repository containing <strong>38 ready-made persona packs</strong> for AI coding tools. Each persona is a folder with two hidden subdirectories:</p>
<pre><code class="hljs language-text">senior-python-developer/
├── .claude/
│   ├── rules/       ← always-on instructions Claude Code reads at session start
│   └── skills/      ← auto-triggered skill docs for specific tasks
└── .cursor/
    ├── rules/       ← always-on instructions Cursor reads at session start
    └── skills/      ← auto-triggered skill docs for specific tasks
</code></pre>
<p>When you copy <code>.claude/</code> or <code>.cursor/</code> (or both) into the root of your project, your editor loads those configs automatically. No slash commands. No system prompts. The persona is live the moment you start a new chat session.</p>
<h3 id="supported-tools">Supported tools</h3>
<table>
<thead>
<tr>
<th>Tool</th>
<th>What to copy</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Claude Code</strong></td>
<td><code>.claude/</code> folder</td>
</tr>
<tr>
<td><strong>Cursor</strong></td>
<td><code>.cursor/</code> folder</td>
</tr>
<tr>
<td><strong>VS Code</strong></td>
<td><code>.claude/</code> folder (when using Claude-based extensions)</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="how-to-use-it-step-by-step">How to Use It — Step by Step</h2>
<h3 id="step-1-clone-the-repo">Step 1: Clone the repo</h3>
<pre><code class="hljs language-bash">git <span class="hljs-built_in">clone</span> https://github.com/ratnesh-maurya/cursor-claude-personas.git
</code></pre>
<p>You only need to clone it once. Keep it around as your personal AI persona library.</p>
<h3 id="step-2-pick-the-persona-that-matches-your-current-work">Step 2: Pick the persona that matches your current work</h3>
<p>Browse the table in the next section or visit the <a href="https://ratnesh-maurya.github.io/cursor-claude-personas/">GitHub Pages site</a> to filter by technology or tag.</p>
<h3 id="step-3-copy-the-persona-into-your-project">Step 3: Copy the persona into your project</h3>
<p>Navigate to your cloned copy and run:</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Claude Code only</span>
<span class="hljs-built_in">cp</span> -R cursor-claude-personas/senior-python-developer/.claude /path/to/your-project/

<span class="hljs-comment"># Cursor only</span>
<span class="hljs-built_in">cp</span> -R cursor-claude-personas/senior-python-developer/.cursor /path/to/your-project/

<span class="hljs-comment"># Both tools</span>
<span class="hljs-built_in">cp</span> -R cursor-claude-personas/senior-python-developer/.claude /path/to/your-project/
<span class="hljs-built_in">cp</span> -R cursor-claude-personas/senior-python-developer/.cursor /path/to/your-project/
</code></pre>
<p>Replace <code>senior-python-developer</code> with any persona slug from the list below.</p>
<h3 id="step-4-reopen-your-editor-or-start-a-new-session">Step 4: Reopen your editor or start a new session</h3>
<p>Claude Code and Cursor reload configuration at session start. From that point on, the AI follows the persona's rules, applies its skills automatically, and behaves as a domain expert — without you typing a single extra word.</p>
<h3 id="bonus-enable-the-auto-skill-router">Bonus: Enable the auto-skill router</h3>
<p>Each persona includes a special rule file called <code>rules--auto-skill-router.mdc</code> (<code>.mdc</code> is the Cursor/Claude rule file format — it is just Markdown with optional YAML frontmatter). This rule tells the AI to automatically select the right skill doc based on what you're working on — so you never need to type <code>/skill</code> commands manually.</p>
<p>It is already included when you copy the persona folders. No extra steps required.</p>
<hr>
<h2 id="all-38-personas">All 38 Personas</h2>
<table>
<thead>
<tr>
<th>Persona</th>
<th>Best for</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>3d-frontend-developer</code></td>
<td>Three.js, WebGL, 3D UI</td>
</tr>
<tr>
<td><code>ai-agent-developer</code></td>
<td>LLM agents, tool use, orchestration</td>
</tr>
<tr>
<td><code>ai-ml-engineer</code></td>
<td>Model training, pipelines, MLOps</td>
</tr>
<tr>
<td><code>azure-developer</code></td>
<td>Azure services, ARM/Bicep, AKS</td>
</tr>
<tr>
<td><code>blockchain-web3-developer</code></td>
<td>Smart contracts, DeFi, Web3 tooling</td>
</tr>
<tr>
<td><code>blog-website-developer</code></td>
<td>Content sites, CMS, static generators</td>
</tr>
<tr>
<td><code>data-engineer</code></td>
<td>Pipelines, dbt, Spark, warehouses</td>
</tr>
<tr>
<td><code>devops-cloud-engineer</code></td>
<td>CI/CD, Kubernetes, IaC, observability</td>
</tr>
<tr>
<td><code>ecommerce-specialist</code></td>
<td>Shopify, WooCommerce, checkout flows</td>
</tr>
<tr>
<td><code>fullstack-saas-mvp</code></td>
<td>Rapid SaaS prototyping, full-stack</td>
</tr>
<tr>
<td><code>functional-programming</code></td>
<td>Haskell, Clojure, FP patterns</td>
</tr>
<tr>
<td><code>game-developer</code></td>
<td>Unity, Godot, game loops</td>
</tr>
<tr>
<td><code>health-tech-developer</code></td>
<td>FHIR, HL7, medical data systems</td>
</tr>
<tr>
<td><code>legal-finance-analyst</code></td>
<td>Contracts, compliance, financial models</td>
</tr>
<tr>
<td><code>marketing-automation</code></td>
<td>Email, CRM integrations, analytics</td>
</tr>
<tr>
<td><code>mobile-developer</code></td>
<td>React Native, Flutter, iOS/Android</td>
</tr>
<tr>
<td><code>n8n-automation-specialist</code></td>
<td>n8n workflows, low-code automation</td>
</tr>
<tr>
<td><code>odoo-erp-specialist</code></td>
<td>Odoo modules, ERP customization</td>
</tr>
<tr>
<td><code>open-source-maintainer</code></td>
<td>OSS governance, reviews, releases</td>
</tr>
<tr>
<td><code>platform-integrations</code></td>
<td>APIs, webhooks, third-party services</td>
</tr>
<tr>
<td><code>product-manager</code></td>
<td>Roadmaps, specs, user stories</td>
</tr>
<tr>
<td><code>qa-testing-engineer</code></td>
<td>Test strategy, automation, coverage</td>
</tr>
<tr>
<td><code>saas-startup-founder</code></td>
<td>GTM, metrics, early-stage decisions</td>
</tr>
<tr>
<td><code>security-engineer</code></td>
<td>Threat modeling, hardening, audits</td>
</tr>
<tr>
<td><code>senior-dotnet-developer</code></td>
<td>C#, ASP.NET Core, Azure</td>
</tr>
<tr>
<td><code>senior-elixir-developer</code></td>
<td>Elixir, Phoenix, OTP</td>
</tr>
<tr>
<td><code>senior-frontend-developer</code></td>
<td>React, TypeScript, frontend architecture</td>
</tr>
<tr>
<td><code>senior-golang-developer</code></td>
<td>Go, microservices, concurrency</td>
</tr>
<tr>
<td><code>senior-java-developer</code></td>
<td>Java, Spring Boot, enterprise patterns</td>
</tr>
<tr>
<td><code>senior-python-developer</code></td>
<td>Python, APIs, automation, data</td>
</tr>
<tr>
<td><code>senior-rust-developer</code></td>
<td>Rust, systems programming, WASM</td>
</tr>
<tr>
<td><code>seo-specialist</code></td>
<td>Technical SEO, Core Web Vitals, schema</td>
</tr>
<tr>
<td><code>system-architect</code></td>
<td>Service boundaries, scalability, design</td>
</tr>
<tr>
<td><code>system-designer</code></td>
<td>Low-level system design, algorithms</td>
</tr>
<tr>
<td><code>technical-writer</code></td>
<td>Docs, onboarding, API clarity</td>
</tr>
<tr>
<td><code>ui-ux-designer</code></td>
<td>Design systems, accessibility, UX</td>
</tr>
<tr>
<td><code>voice-ai-developer</code></td>
<td>Voice interfaces, TTS, STT, ASR</td>
</tr>
<tr>
<td><code>wordpress-specialist</code></td>
<td>WordPress, WooCommerce, theme dev</td>
</tr>
</tbody>
</table>
<p>Not sure where to start? Try <code>senior-python-developer</code> or <code>senior-frontend-developer</code> — both are polished starting points that work for a wide range of projects.</p>
<hr>
<h2>What Makes This Different From a <code>.cursorrules</code> File?</h2>
<p>A <code>.cursorrules</code> file (a single plain-text file at the project root) you write yourself usually contains two or three paragraphs of general instructions. Cursor also supports a <code>.cursor/rules/</code> directory of individual rule files for more granular control. Both are fine for broad guidance, but neither gives your AI assistant real domain depth out of the box.</p>
<p>cursor-claude-personas packs in:</p>
<ul>
<li><strong>Multiple focused rule files</strong> — each covering a specific concern (schema design, security, pagination, query patterns, etc.) rather than one sprawling blob of text.</li>
<li><strong>Skill documents</strong> — structured reference files that are automatically surfaced when the AI detects it needs them, without you prompting it.</li>
<li><strong>Both tool formats simultaneously</strong> — the same expertise loads whether you are using Claude Code, Cursor, or VS Code Copilot.</li>
<li><strong>Zero prompt engineering on your part</strong> — you copy two folders and you are done.</li>
</ul>
<p>The key design principle is <strong>depth over breadth</strong>: one sharp specialist beats five vague generalists.</p>
<hr>
<h2 id="real-world-example">Real-World Example</h2>
<p>Imagine you are building a Python REST API with PostgreSQL. You copy in the <code>senior-python-developer</code> persona. From that point on, when you write a query, the AI:</p>
<ul>
<li>Warns you against <code>WHERE id IN (SELECT ...)</code> and rewrites it as <code>WHERE id = ANY(ARRAY[...])</code> with a note about the 10× speedup.</li>
<li>Adds <code>WHERE deleted_at IS NULL</code> partial indexes automatically when it sees soft-delete columns.</li>
<li>Flags missing foreign key indexes before you even run <code>EXPLAIN ANALYZE</code>.</li>
<li>Enforces proper connection pool limits and prepared statement patterns.</li>
</ul>
<p>None of that required you to type a single instruction. It was all encoded in the rules and skills you copied in.</p>
<hr>
<h2 id="how-the-folder-structure-works-under-the-hood">How the Folder Structure Works Under the Hood</h2>
<p>Each persona ships with two kinds of config files:</p>
<h3>Rules (<code>rules/</code>)</h3>
<p>Rules are always-on markdown files the editor reads at the start of every session. They define the persona's defaults, code style, anti-patterns to avoid, and domain-specific best practices. They are named with a <code>rules--</code> prefix so they sort together and are easy to identify.</p>
<h3>Skills (<code>skills/</code>)</h3>
<p>Skill documents are structured markdown files with YAML frontmatter. The auto-skill router rule tells the AI to load the relevant skill when it detects you are working on a related task. For example, if you start writing an index migration, the SQL indexing skill is surfaced automatically. If you start wiring up an authentication flow, the auth skill fires.</p>
<p>This means the AI pulls in deep, specific guidance exactly when it is relevant — not all at once, cluttering its context.</p>
<hr>
<h2 id="switching-personas-mid-project">Switching Personas Mid-Project</h2>
<p>You can swap personas at any time. Just delete the existing <code>.claude/</code> and <code>.cursor/</code> folders in your project and copy in a different persona. Reopen your session and the new persona is live.</p>
<p>You can even mix rules from multiple personas manually if you know what you are doing, but most of the time a single focused persona is all you need.</p>
<hr>
<h2 id="contributing-a-new-persona">Contributing a New Persona</h2>
<p>The project is open source under the MIT license and welcomes contributions. If you want to add a new persona:</p>
<ol>
<li>Fork the repository.</li>
<li>Create a new folder named after the role (e.g., <code>data-science-researcher</code>).</li>
<li>Add matching <code>.claude/</code> and <code>.cursor/</code> subdirectories with rules and skills.</li>
<li>Make sure the two directories are in sync — the parity check in <code>CONTRIBUTING.md</code> validates this.</li>
<li>Open a pull request.</li>
</ol>
<p>See <a href="CONTRIBUTING.md">CONTRIBUTING.md</a> for branch naming conventions, commit message format, and the full persona checklist.</p>
<hr>
<h2 id="where-to-get-it">Where to Get It</h2>
<ul>
<li><strong>GitHub repository</strong>: <a href="https://github.com/ratnesh-maurya/cursor-claude-personas">https://github.com/ratnesh-maurya/cursor-claude-personas</a></li>
<li><strong>GitHub Pages site</strong> (browse and filter personas): <a href="https://ratnesh-maurya.github.io/cursor-claude-personas/">https://ratnesh-maurya.github.io/cursor-claude-personas/</a></li>
<li><strong>License</strong>: MIT — free for personal and commercial use.</li>
</ul>
<p>If it saves you time, star the repo. It helps other developers discover it.</p>
<hr>
<h2 id="summary">Summary</h2>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>What it is</strong></td>
<td>38 role-based AI persona packs for Claude Code, Cursor, and VS Code</td>
</tr>
<tr>
<td><strong>What it does</strong></td>
<td>Shapes your AI coding assistant into a domain specialist — instantly</td>
</tr>
<tr>
<td><strong>How to install</strong></td>
<td>Clone the repo, copy one persona folder, reopen your editor</td>
</tr>
<tr>
<td><strong>Time to set up</strong></td>
<td>Under 30 seconds</td>
</tr>
<tr>
<td><strong>Cost</strong></td>
<td>Free and open source (MIT)</td>
</tr>
<tr>
<td><strong>Requires</strong></td>
<td>Claude Code, Cursor, or VS Code with Copilot</td>
</tr>
</tbody>
</table>
<p>Clone it, copy a persona, and ship better code.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/cursor-claude-personas-give-your-ai-coding-assistant-a-domain-expert-brain-in-30-seconds</link>
        <guid>https://blog.ratnesh-maurya.com/blog/cursor-claude-personas-give-your-ai-coding-assistant-a-domain-expert-brain-in-30-seconds</guid>
        <pubDate>Sat, 21 Mar 2026 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>Tooling and DX</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/cursor-claude-personas.png" length="87306" type="image/png" />
      </item>
      <item>
        <title><![CDATA[Five Caching Strategies Every Backend Dev Should Know]]></title>
        <description><![CDATA[A beginner-friendly guide to Cache-Aside, Read-Through, Write-Through, Write-Back, and Write-Around, with when to use each.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/Distributed-Caching-Strategies-Architecture.png" alt="Five Caching Strategies Every Backend Dev Should Know" /></p><h1>Five Caching Strategies Every Backend Dev Should Know</h1>
<p>If you build backend systems, you eventually run into <strong>caching</strong>.</p>
<p>Someone says “just put Redis in front of it” and suddenly your app is faster… until you start seeing stale data, cache misses, and weird bugs when you deploy.</p>
<p>This post explains <strong>five core caching strategies</strong> in plain language:</p>
<ul>
<li>Cache-Aside</li>
<li>Read-Through</li>
<li>Write-Through</li>
<li>Write-Back</li>
<li>Write-Around</li>
</ul>
<p>You’ll see <strong>how each one works</strong>, <strong>what can go wrong</strong>, and <strong>when to use it</strong>.</p>
<p>A cache is just a faster, smaller copy of some data.
The hard part is <strong>keeping that copy useful</strong>: when to fill it, when to read from it, and when to update or skip it.</p>
<hr>
<h2>1. Cache-Aside (Lazy Loading)</h2>
<p><strong>Idea:</strong>
The application talks to the cache and the database.
It “checks the cache first, then falls back to the DB” and fills the cache on a miss.</p>
<pre><code>If the key is in the cache (a hit), just return it. Super fast.


If the key is missing, the app queries the database instead.


The app stores the result in the cache and returns it to the user.
</code></pre>
<p><strong>Why people love it</strong></p>
<ul>
<li><strong>Simple and flexible</strong>: logic lives in your code; you control what goes in the cache.</li>
<li><strong>Resilient</strong>: if the cache dies, the app can still read from the DB (just slower).</li>
<li><strong>Memory-efficient</strong>: only data that is actually read gets cached.</li>
</ul>
<p><strong>What can go wrong</strong></p>
<ul>
<li>The <strong>first</strong> request for a key is slow (cold cache).</li>
<li>If the DB is updated <strong>outside</strong> your code path (e.g. admin tool, another service), the cache can serve <strong>stale data</strong> until TTL expires or you manually invalidate.</li>
</ul>
<p><strong>When to use</strong></p>
<ul>
<li>Great default for <strong>read-heavy</strong> apps (blogs, product catalogs, many dashboards).</li>
<li>When you’re just adding caching to an existing codebase and want full control.</li>
</ul>
<hr>
<h2>2. Read-Through</h2>
<p><strong>Idea:</strong>
The application pretends the <strong>cache is the main database</strong>.</p>
<p>It calls the cache for every read. On a miss, the <strong>cache layer itself</strong> fetches from the DB, stores the result, and returns it.</p>
<p>With <strong>Cache-Aside</strong>, your app code calls both cache and DB.
With <strong>Read-Through</strong>, your app only calls the cache; a loader behind the cache talks to the DB.</p>
<p><strong>Pros</strong></p>
<ul>
<li><strong>Cleaner application code</strong>: your code only knows “get from cache”.</li>
<li>Cache libraries or infrastructure can handle loading, retries, and metrics.</li>
</ul>
<p><strong>Cons</strong></p>
<ul>
<li>If some writes go <strong>directly to the DB</strong> (bypassing the cache API), the cache can easily become <strong>stale</strong>.</li>
<li>You need a cache provider or library that supports read-through loading.</li>
</ul>
<p><strong>When to use</strong></p>
<ul>
<li>Larger teams where you want <strong>centralized cache logic</strong>, not ad-hoc caching scattered everywhere.</li>
<li>Situations where infra/platform team owns the cache layer.</li>
</ul>
<hr>
<h2>3. Write-Through</h2>
<p>So far we talked about reads.
Now let’s talk about <strong>writes</strong>: how we keep cache and DB in sync.</p>
<p><strong>Write-Through</strong> means:</p>
<blockquote>
<p>On a write, update the cache <strong>and</strong> the database synchronously.
Only when both succeed is the write considered “done”.</p>
</blockquote>
<p><strong>Flow</strong></p>
<ol>
<li>User updates something (e.g. changes quantity in a cart).</li>
<li>App writes the new value into the <strong>cache</strong>.</li>
<li>App also writes the same value into the <strong>database</strong>.</li>
<li>Only if both succeed do we return “OK” to the user.</li>
</ol>
<p><strong>Pros</strong></p>
<ul>
<li><strong>Strong consistency</strong>: cache and DB always match for that key.</li>
<li>Reads from the cache always see the latest value.</li>
</ul>
<p><strong>Cons</strong></p>
<ul>
<li><strong>Slower writes</strong>: every write has to hit both cache and DB before returning.</li>
<li>If DB is slow, your API write latency is also slow.</li>
</ul>
<p><strong>When to use</strong></p>
<ul>
<li>When correctness matters more than raw speed:
<ul>
<li>balances in a <strong>wallet / bank</strong>,</li>
<li><strong>inventory</strong> in an e‑commerce site,</li>
<li>data where a stale read would be really bad.</li>
</ul>
</li>
</ul>
<hr>
<h2>4. Write-Back (Write-Behind)</h2>
<p><strong>Write-Back</strong> trades strict consistency for <strong>speed</strong>.</p>
<blockquote>
<p>On a write, update <strong>only the cache</strong> and return success quickly.
Later, an async process flushes those changes to the database.</p>
</blockquote>
<p><strong>Flow</strong></p>
<ol>
<li>User sends a write.</li>
<li>App writes to cache and returns <code>200 OK</code> immediately.</li>
<li>A background worker or the cache itself periodically writes batched updates into the DB.</li>
</ol>
<p><strong>Pros</strong></p>
<ul>
<li><strong>Very fast writes</strong>: app isn’t blocked waiting for DB.</li>
<li>Great for high-throughput workloads:
<ul>
<li>counters (views, likes),</li>
<li>logs / telemetry,</li>
<li>social feed events.</li>
</ul>
</li>
</ul>
<p><strong>Cons</strong></p>
<ul>
<li><strong>Risk of data loss</strong>: if the cache crashes before flushing to DB, those changes are gone.</li>
<li>DB is <strong>eventually</strong> updated, not immediately.</li>
</ul>
<p><strong>When to use</strong></p>
<ul>
<li>Write-heavy workloads where losing a tiny fraction of data is acceptable:
<ul>
<li>metrics, dashboards, click-streams,</li>
<li>non-critical logs.</li>
</ul>
</li>
</ul>
<hr>
<h2>5. Write-Around</h2>
<p><strong>Write-Around</strong> takes a different approach:</p>
<blockquote>
<p>Writes <strong>skip the cache</strong> and go straight to the DB.
The cache is only filled later when data is read.</p>
</blockquote>
<p><strong>Flow</strong></p>
<ol>
<li>A write goes <strong>directly</strong> into the database.</li>
<li>The cache is <strong>not</strong> updated.</li>
<li>On the next read, the app (or cache) sees a <strong>miss</strong>:
<ul>
<li>loads from DB,</li>
<li>populates the cache,</li>
<li>and returns the value.</li>
</ul>
</li>
</ol>
<p><strong>Pros</strong></p>
<ul>
<li>Prevents “polluting” the cache with data that is <strong>written but rarely read</strong>.</li>
<li>Good when you have big write batches (imports, ETL jobs) that users won’t read immediately.</li>
</ul>
<p><strong>Cons</strong></p>
<ul>
<li>The <strong>first read</strong> after a write is always a <strong>cache miss</strong> (slower).</li>
<li>More frequent DB reads if many keys are only read once.</li>
</ul>
<p><strong>When to use</strong></p>
<ul>
<li>Large imports, archival data, logs that might be read only occasionally.</li>
<li>Systems where memory is tight and you want the cache to focus on <strong>truly hot</strong> data.</li>
</ul>
<hr>
<h2>Side-by-side comparison</h2>
<p>Here’s a quick comparison of these five strategies using the labels you provided.</p>
<hr>
<h2>How teams typically choose</h2>
<p>To connect this with the interactive content you built:</p>
<p>These charts show two things:</p>
<ul>
<li><strong>Cache-Aside</strong> dominates in real systems because it’s <strong>simple and resilient</strong>.</li>
<li><strong>Write-Back</strong> gives the fastest writes but carries the most risk;
<strong>Write-Through</strong> is safest but slower on each write.</li>
</ul>
<hr>
<h2>Which caching strategy should you pick? (a practical rule-set)</h2>
<p>If you only remember one thing from this post, remember this:
<strong>you pick a caching strategy based on the cost of a stale read and the cost of a slow write.</strong></p>
<p>Here’s a fast, real-world decision guide:</p>
<ul>
<li>
<p><strong>Most read-heavy apps</strong> (catalogs, blogs, dashboards):</p>
<ul>
<li>Start with <strong>Cache-Aside</strong>.</li>
<li>Add <strong>TTLs</strong> and a plan for <strong>invalidation</strong> if data changes.</li>
</ul>
</li>
<li>
<p><strong>You want a clean abstraction and centralized caching</strong> (platform/infra owned):</p>
<ul>
<li>Use <strong>Read-Through</strong> so product code only depends on the cache API.</li>
</ul>
</li>
<li>
<p><strong>A stale read would be painful</strong> (wallet balances, inventory, “did my payment go through?”):</p>
<ul>
<li>Prefer <strong>Write-Through</strong> (consistency > speed).</li>
</ul>
</li>
<li>
<p><strong>Writes are hot and you can tolerate some eventual consistency</strong> (counters, analytics, clickstream):</p>
<ul>
<li>Consider <strong>Write-Back</strong>, but only with durability guardrails (below).</li>
</ul>
</li>
<li>
<p><strong>You write lots of data that is rarely read</strong> (imports, ETL, logs):</p>
<ul>
<li>Use <strong>Write-Around</strong> to avoid polluting the cache.</li>
</ul>
</li>
</ul>
<hr>
<h2>Two failure modes teams hit in production (and what to do)</h2>
<p>Caching rarely fails in obvious ways. It fails with “the DB is melting” or “why is data stale?”.
Two patterns show up everywhere.</p>
<h3>1) Cache stampede (thundering herd)</h3>
<p><strong>Symptom:</strong> a hot key expires (or cache restarts), and suddenly thousands of requests miss at once and slam the DB.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li><strong>Request coalescing / single-flight</strong>: only one request recomputes the value; others wait and reuse it.</li>
<li><strong>Stale-while-revalidate</strong>: keep serving a slightly stale value while one background refresh updates it.</li>
<li><strong>Jittered TTLs</strong>: add randomization to expiration times so many keys don’t expire at the same second.</li>
</ul>
<h3>2) Hot key / hotspotting</h3>
<p><strong>Symptom:</strong> one key becomes so popular (home feed, trending item, auth config) that a single cache shard or a single DB row gets hammered.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li><strong>Shard the key</strong> (e.g. <code>trending:v1:0..N</code>) and merge results.</li>
<li>Use a <strong>two-layer cache</strong>: per-instance in-memory (tiny TTL) + shared Redis.</li>
<li><strong>Precompute</strong> or cache at a higher level (edge/CDN) if the data is public.</li>
</ul>
<hr>
<h2>What to monitor so caching doesn’t become “magic”</h2>
<p>Whatever strategy you choose, watch these:</p>
<ul>
<li><strong>Cache hit rate</strong> (overall and by endpoint)</li>
<li><strong>DB QPS</strong> (does caching actually reduce it?)</li>
<li><strong>p95/p99 latency</strong> (cache helps only if tail latency improves)</li>
<li><strong>Eviction rate / memory pressure</strong> (are you constantly evicting hot data?)</li>
<li><strong>Stale reads / correctness signals</strong> (app-specific: inventory mismatches, stale dashboards, etc.)</li>
</ul>
<p>If you can’t measure these, caching will eventually surprise you.</p>
<hr>
<h2>Putting it all together (for a newbie)</h2>
<p>If you’re just starting with caching:</p>
<ol>
<li>
<p><strong>Start with Cache-Aside</strong> for reads.
It’s simple, safe, and easy to reason about.</p>
</li>
<li>
<p><strong>Add Read-Through</strong> if you want a cleaner abstraction and your cache provider supports it.</p>
</li>
<li>
<p>For writes:</p>
<ul>
<li>Use <strong>Write-Through</strong> when you absolutely need the cache and DB to agree on every write.</li>
<li>Use <strong>Write-Back</strong> when you care a lot about write speed and can tolerate a little risk.</li>
<li>Use <strong>Write-Around</strong> when you write lots of data that users won’t read immediately.</li>
</ul>
</li>
<li>
<p>Whatever you choose, always:</p>
<ul>
<li>Set reasonable <strong>TTLs</strong>,</li>
<li>Have a plan for <strong>invalidating</strong> keys,</li>
<li>And monitor <strong>hit rate</strong> and <strong>latency</strong> so you can adjust over time.</li>
</ul>
</li>
</ol>
<p>With just these five strategies, you can handle most caching problems you’ll run into as a backend engineer.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/Distributed-Caching-Strategies-Architecture</link>
        <guid>https://blog.ratnesh-maurya.com/blog/Distributed-Caching-Strategies-Architecture</guid>
        <pubDate>Tue, 24 Feb 2026 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>Backend</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/Distributed-Caching-Strategies-Architecture.png" length="2007557" type="image/png" />
      </item>
      <item>
        <title><![CDATA[Chrome 144's <geolocation> Element: Declarative Location Access]]></title>
        <description><![CDATA[Learn how Chrome 144's new <geolocation> HTML element simplifies location access, improves privacy, and replaces imperative navigator.geolocation calls with a declarative, user-driven approach.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/chrome-144-introduces-the-geolocation-element-declarative-location-access-for-modern-web-apps.png" alt="Chrome 144's &lt;geolocation&gt; Element: Declarative Location Access" /></p><h1>Chrome 144 Introduces the <code>&#x3C;geolocation></code> Element: Declarative Location Access for Modern Web Apps</h1>
<p>Chrome 144 introduces a new declarative <code>&#x3C;geolocation></code> HTML element that changes how web applications request location access. Instead of calling <code>navigator.geolocation.getCurrentPosition()</code> imperatively from JavaScript, developers can now place a browser-controlled element directly in the DOM.</p>
<p>This post is for frontend engineers, platform engineers, and privacy-conscious developers who want to understand how this new model works and whether they should migrate.</p>
<p>By the end of this article, you'll understand:</p>
<ul>
<li>What the <code>&#x3C;geolocation></code> element is</li>
<li>How it works internally</li>
<li>How it compares to the traditional Geolocation API</li>
<li>How to implement it with proper fallback support</li>
</ul>
<hr>
<h2>Why Chrome Introduced <code>&#x3C;geolocation></code></h2>
<p>Historically, location access was triggered via JavaScript:</p>
<pre><code class="hljs language-js">navigator.<span class="hljs-property">geolocation</span>.<span class="hljs-title function_">getCurrentPosition</span>(success, error);
</code></pre>
<p>This caused several problems:</p>
<ul>
<li>Permission prompts appearing on page load</li>
<li>Users denying requests due to poor timing</li>
<li>Quiet permission blocking by browsers</li>
<li>Complicated permission state handling in code</li>
</ul>
<p>The <code>&#x3C;geolocation></code> element solves this by:</p>
<ul>
<li>Requiring explicit user interaction</li>
<li>Providing a visible, browser-controlled UI</li>
<li>Reducing boilerplate permission code</li>
<li>Improving recovery from previously denied states</li>
</ul>
<p>Chrome origin trials showed measurable improvements in successful permission grants and recovery rates.</p>
<hr>
<h2>What Is the <code>&#x3C;geolocation></code> Element?</h2>
<p>The <code>&#x3C;geolocation></code> element is part of the WICG "Permission Elements" proposal. It acts as a declarative wrapper around the existing Geolocation API.</p>
<p>It renders as a browser-controlled button such as:</p>
<p>When clicked:</p>
<ol>
<li>The browser shows the location permission dialog.</li>
<li>If granted, it dispatches a <code>location</code> event.</li>
<li>The element exposes either:
<ul>
<li><code>position</code> (success)</li>
<li><code>error</code> (failure)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="attributes-explained">Attributes Explained</h2>
<h3><code>autolocate</code></h3>
<p>Automatically attempts to retrieve location when inserted into the DOM — <strong>only if permission was already granted</strong>.</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">geolocation</span> <span class="hljs-attr">autolocate</span>></span><span class="hljs-tag">&#x3C;/<span class="hljs-name">geolocation</span>></span>
</code></pre>
<p>It does not trigger surprise permission prompts.</p>
<hr>
<h3><code>watch</code></h3>
<p>Continuously tracks position updates, similar to <code>watchPosition()</code>.</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">geolocation</span> <span class="hljs-attr">watch</span>></span><span class="hljs-tag">&#x3C;/<span class="hljs-name">geolocation</span>></span>
</code></pre>
<p>Without <code>watch</code>, it behaves like <code>getCurrentPosition()</code>.</p>
<hr>
<h3><code>accuracymode</code></h3>
<p>Controls precision level.</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">geolocation</span> <span class="hljs-attr">accuracymode</span>=<span class="hljs-string">"precise"</span>></span><span class="hljs-tag">&#x3C;/<span class="hljs-name">geolocation</span>></span>
</code></pre>
<p>Possible values:</p>
<ul>
<li><code>"approximate"</code> (default)</li>
<li><code>"precise"</code></li>
</ul>
<p>When set to <code>"precise"</code>, the browser may:</p>
<ul>
<li>Change icon (crosshair instead of pin)</li>
<li>Update label text</li>
<li>Request higher accuracy internally</li>
</ul>
<hr>
<h2 id="basic-implementation">Basic Implementation</h2>
<p>Minimal example:</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">geolocation</span> <span class="hljs-attr">onlocation</span>=<span class="hljs-string">"handleLocation(event)"</span>></span><span class="hljs-tag">&#x3C;/<span class="hljs-name">geolocation</span>></span>

<span class="hljs-tag">&#x3C;<span class="hljs-name">script</span>></span><span class="javascript">
<span class="hljs-keyword">function</span> <span class="hljs-title function_">handleLocation</span>(<span class="hljs-params">event</span>) {
  <span class="hljs-keyword">if</span> (event.<span class="hljs-property">target</span>.<span class="hljs-property">position</span>) {
    <span class="hljs-keyword">const</span> { latitude, longitude } = event.<span class="hljs-property">target</span>.<span class="hljs-property">position</span>.<span class="hljs-property">coords</span>;
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Location:"</span>, latitude, longitude);
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.<span class="hljs-property">target</span>.<span class="hljs-property">error</span>) {
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">"Error:"</span>, event.<span class="hljs-property">target</span>.<span class="hljs-property">error</span>.<span class="hljs-property">message</span>);
  }
}
</span><span class="hljs-tag">&#x3C;/<span class="hljs-name">script</span>></span>
</code></pre>
<p>No permission logic required.</p>
<p>The browser handles:</p>
<ul>
<li>Permission prompts</li>
<li>Recovery dialogs</li>
<li>Error states</li>
</ul>
<hr>
<h2 id="adding-progressive-enhancement-fallback">Adding Progressive Enhancement (Fallback)</h2>
<p>You must support non-Chrome browsers.</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">geolocation</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"geo"</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"fallback-btn"</span>></span>
    Use my location
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">geolocation</span>></span>

<span class="hljs-tag">&#x3C;<span class="hljs-name">script</span>></span><span class="javascript">
<span class="hljs-keyword">if</span> (<span class="hljs-string">'HTMLGeolocationElement'</span> <span class="hljs-keyword">in</span> <span class="hljs-variable language_">window</span>) {
  <span class="hljs-keyword">const</span> geo = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'geo'</span>);
  geo.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'location'</span>, <span class="hljs-function">() =></span> {
    <span class="hljs-keyword">if</span> (geo.<span class="hljs-property">position</span>) {
      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(geo.<span class="hljs-property">position</span>.<span class="hljs-property">coords</span>);
    }
  });
} <span class="hljs-keyword">else</span> {
  <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'fallback-btn'</span>)
    .<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =></span> {
      navigator.<span class="hljs-property">geolocation</span>.<span class="hljs-title function_">getCurrentPosition</span>(<span class="hljs-function">(<span class="hljs-params">pos</span>) =></span> {
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(pos.<span class="hljs-property">coords</span>);
      });
    });
}
</span><span class="hljs-tag">&#x3C;/<span class="hljs-name">script</span>></span>
</code></pre>
<p>This ensures compatibility across browsers.</p>
<hr>
<h2 id="browser-ui-behavior">Browser UI Behavior</h2>
<p>When clicked, Chrome displays the standard location permission dialog:</p>
<p>If previously denied, Chrome shows a recovery prompt:</p>
<p>This makes permission recovery significantly easier compared to manual browser settings navigation.</p>
<hr>
<h2 id="styling-restrictions-security-guardrails">Styling Restrictions (Security Guardrails)</h2>
<p>To prevent abuse or clickjacking, Chrome enforces:</p>
<ul>
<li>Minimum size</li>
<li>Opacity must remain <code>1</code></li>
<li>No negative margins</li>
<li>No deceptive transforms</li>
<li>Enforced contrast ratio</li>
</ul>
<p>You can style it, but you cannot make it invisible or misleading.</p>
<p>Example:</p>
<pre><code class="hljs language-css">geolocation {
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">12px</span>;
}
</code></pre>
<p>But you cannot:</p>
<ul>
<li>Hide it</li>
<li>Make it 1px wide</li>
<li>Overlay fake UI elements</li>
</ul>
<hr>
<h2><code>&#x3C;geolocation></code> vs <code>navigator.geolocation</code></h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Traditional API</th>
<th><code>&#x3C;geolocation></code></th>
</tr>
</thead>
<tbody>
<tr>
<td>Permission Trigger</td>
<td>Script-based</td>
<td>User-click only</td>
</tr>
<tr>
<td>Boilerplate Code</td>
<td>High</td>
<td>Minimal</td>
</tr>
<tr>
<td>Recovery Flow</td>
<td>Manual</td>
<td>Built-in</td>
</tr>
<tr>
<td>Styling Control</td>
<td>Full</td>
<td>Guard-railed</td>
</tr>
<tr>
<td>Privacy Model</td>
<td>Developer-controlled</td>
<td>Browser-mediated</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="when-should-you-use-it">When Should You Use It?</h2>
<p>Use <code>&#x3C;geolocation></code> if:</p>
<ul>
<li>You need a simple "Use my location" feature</li>
<li>You want better permission recovery</li>
<li>You want reduced code complexity</li>
<li>You care about user trust and privacy</li>
</ul>
<p>Stick to the traditional API if:</p>
<ul>
<li>You need fine-grained timing control</li>
<li>You require silent background tracking</li>
<li>You are building real-time geo-heavy apps (ride-sharing, logistics dashboards)</li>
</ul>
<hr>
<h2 id="production-checklist">Production Checklist</h2>
<ul>
<li>Feature-detect <code>HTMLGeolocationElement</code></li>
<li>Provide a JS fallback</li>
<li>Avoid using <code>autolocate</code> unnecessarily</li>
<li>Test denied → recovery flows</li>
<li>Validate UX on mobile devices</li>
</ul>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>The <code>&#x3C;geolocation></code> element is a significant shift toward declarative permission handling on the web. It reduces developer complexity while improving privacy, trust, and user experience.</p>
<p>For simple location-based features, it should become the default pattern in Chrome-supported environments.</p>
<p>However, since it's currently Chrome-specific and incubating, production systems must still include fallback support.</p>
<p>If you're building modern web applications and care about privacy-first UX, this is worth adopting early — but do it responsibly with progressive enhancement.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/Chrome-144-Introduces-the-Geolocation-Element-Declarative-Location-Access-for-Modern-Web-Apps</link>
        <guid>https://blog.ratnesh-maurya.com/blog/Chrome-144-Introduces-the-Geolocation-Element-Declarative-Location-Access-for-Modern-Web-Apps</guid>
        <pubDate>Tue, 24 Feb 2026 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>Web Development</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/chrome-144-introduces-the-geolocation-element-declarative-location-access-for-modern-web-apps.png" length="1843006" type="image/png" />
      </item>
      <item>
        <title><![CDATA[How Files Are Stored, Deleted, and Copied Inside Your Computer]]></title>
        <description><![CDATA[Understand how files are actually stored on disk, why deletion is instant, and why copying takes time. A beginner-friendly deep dive into file systems and storage architecture.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/How-Files-Are-Stored-Deleted-and-Copied-Inside-Your-Computer.jpg" alt="How Files Are Stored, Deleted, and Copied Inside Your Computer" /></p><h1 id="how-files-are-stored-deleted-and-copied-inside-your-computer">How Files Are Stored, Deleted, and Copied Inside Your Computer</h1>
<p>Have you ever noticed something interesting?</p>
<ul>
<li>Deleting a file happens instantly.</li>
<li>Copying a file takes time.</li>
<li>Moving a file is sometimes instant and sometimes slow.</li>
</ul>
<p>Why does this happen?</p>
<p>If you're new to operating systems and storage internals, this article explains everything in simple terms using real-world analogies, while also giving you a deeper technical understanding of what’s happening behind the scenes.</p>
<p>By the end, you’ll understand:</p>
<ul>
<li>How files are stored on disk</li>
<li>Why delete is fast</li>
<li>Why copy takes time</li>
<li>Whether files are really deleted</li>
<li>How HDD and SSD behave differently</li>
</ul>
<hr>
<h2 id="how-files-are-actually-stored-on-disk">How Files Are Actually Stored on Disk</h2>
<p>Your computer does not store files the way you see them in folders.</p>
<p>Internally, storage is divided into:</p>
<ol>
<li><strong>Data Blocks (or clusters)</strong> – These contain the actual bytes of your file.</li>
<li><strong>Metadata</strong> – Information about the file:
<ul>
<li>File name</li>
<li>File size</li>
<li>Location of data blocks</li>
<li>Permissions</li>
<li>Created/modified timestamps</li>
</ul>
</li>
</ol>
<p>A file system (like NTFS, ext4, FAT32) manages this structure.</p>
<h3 id="example-file-systems">Example File Systems</h3>
<ul>
<li>Windows → NTFS</li>
<li>Linux → ext4</li>
<li>USB drives → FAT32</li>
</ul>
<p>Each filesystem maintains:</p>
<ul>
<li>A directory entry (file name)</li>
<li>A metadata record (inode in Linux, MFT entry in NTFS)</li>
<li>Pointers to the actual disk blocks</li>
</ul>
<p>You can think of it like a database that tracks where your file data lives on disk.</p>
<hr>
<h2 id="real-life-analogy-a-library">Real-Life Analogy: A Library</h2>
<p>Imagine this:</p>
<ul>
<li>The <strong>library catalog</strong> = filesystem metadata</li>
<li>The <strong>books on shelves</strong> = actual file data</li>
<li>The <strong>shelf location number</strong> = pointer to disk blocks</li>
</ul>
<p>When you open a file:</p>
<ol>
<li>The system checks the catalog.</li>
<li>It finds the shelf location.</li>
<li>It retrieves the book (data blocks).</li>
</ol>
<p>Now let’s see what happens during delete and copy.</p>
<hr>
<h2 id="why-deleting-a-file-is-instant">Why Deleting a File Is Instant</h2>
<p>When you delete a file, the system usually does <strong>not erase the data immediately</strong>.</p>
<p>Instead, it:</p>
<ol>
<li>Removes the file name from the directory.</li>
<li>Marks its data blocks as “free”.</li>
<li>Updates metadata records.</li>
</ol>
<p>That’s it.</p>
<p>The actual bytes are still physically on disk — but the system marks that space as available for reuse.</p>
<p>Since this is mainly a metadata update, it happens very fast.</p>
<h3 id="important-insight">Important Insight</h3>
<p>Deleting is like removing the book’s entry from the library catalog.</p>
<p>The book is still on the shelf — but nobody knows it exists anymore.</p>
<p>This is why:</p>
<ul>
<li>File recovery tools can restore deleted files.</li>
<li>Secure deletion requires special tools.</li>
</ul>
<hr>
<h2 id="do-files-actually-get-deleted">Do Files Actually Get Deleted?</h2>
<p>Short answer: <strong>Not immediately.</strong></p>
<p>They are:</p>
<ul>
<li>Marked as free space</li>
<li>Eventually overwritten by new data</li>
</ul>
<p>On SSDs:</p>
<ul>
<li>The operating system sends a <strong>TRIM command</strong></li>
<li>The SSD later clears unused blocks internally</li>
</ul>
<p>Deletion is logical first, physical later.</p>
<hr>
<h2 id="why-copying-a-file-takes-time">Why Copying a File Takes Time</h2>
<p>Copying is completely different.</p>
<p>When copying:</p>
<ol>
<li>The system reads every block from the source file.</li>
<li>Loads it into memory (RAM).</li>
<li>Writes those blocks to a new location.</li>
<li>Creates new metadata for the copied file.</li>
</ol>
<p>This process depends on:</p>
<ul>
<li>Disk speed</li>
<li>File size</li>
<li>HDD vs SSD</li>
<li>CPU and memory performance</li>
</ul>
<p>Since the entire file’s data must be read and written, copying takes time.</p>
<h3 id="example">Example</h3>
<p>Copying a 10GB file means:</p>
<ul>
<li>Reading 10GB</li>
<li>Writing 10GB</li>
</ul>
<p>That’s 20GB of total I/O operations.</p>
<p>That’s real physical work being done.</p>
<hr>
<h2 id="why-moving-files-is-sometimes-instant">Why Moving Files Is Sometimes Instant</h2>
<p>This depends on where you move the file.</p>
<h3 id="case-1-same-drive-same-filesystem">Case 1: Same Drive (Same Filesystem)</h3>
<p>Move = metadata update only.</p>
<p>The system just:</p>
<ul>
<li>Updates the directory entry</li>
<li>Keeps data blocks unchanged</li>
</ul>
<p>This is very fast.</p>
<h3 id="case-2-different-drive">Case 2: Different Drive</h3>
<p>Move becomes:</p>
<ul>
<li>Copy the file</li>
<li>Delete the original</li>
</ul>
<p>So it takes time.</p>
<p>That’s why:</p>
<ul>
<li>Moving inside C: is instant</li>
<li>Moving from C: to D: takes time</li>
</ul>
<hr>
<h2 id="hdd-vs-ssd-behavior">HDD vs SSD Behavior</h2>
<h3 id="hdd-hard-disk-drive">HDD (Hard Disk Drive)</h3>
<ul>
<li>Mechanical spinning disk</li>
<li>Moving read/write head</li>
<li>Slower seek time</li>
<li>Sequential reads are faster</li>
</ul>
<p>Copying large files can be slower because of mechanical movement.</p>
<h3 id="ssd-solid-state-drive">SSD (Solid State Drive)</h3>
<ul>
<li>No moving parts</li>
<li>Much faster random access</li>
<li>Uses flash memory cells</li>
<li>Uses TRIM and garbage collection</li>
</ul>
<p>Deletion marks blocks unused first. The physical erase happens later internally.</p>
<hr>
<h2 id="what-happens-in-the-os-layer">What Happens in the OS Layer</h2>
<p>At the system-call level (Linux example):</p>
<ul>
<li><code>delete</code> → <code>unlink()</code></li>
<li><code>copy</code> → multiple <code>read()</code> and <code>write()</code> calls</li>
<li><code>move (same filesystem)</code> → <code>rename()</code></li>
</ul>
<p>Example:</p>
<pre><code class="hljs language-bash">strace <span class="hljs-built_in">rm</span> file.txt

</code></pre>
<p>You'll see something like:</p>
<p><code>unlink("file.txt")</code></p>
<p>That's simply removing the reference to the file.</p>
<p>Copying involves many <code>read()</code> and <code>write()</code> operations, which take time.</p>
<hr>
<h2 id="why-deleting-many-files-can-be-slow">Why Deleting Many Files Can Be Slow</h2>
<p>Deleting one file is fast.</p>
<p>Deleting thousands of small files can take time because:</p>
<ul>
<li>Each file has metadata.</li>
<li>Directory entries must be updated.</li>
<li>Journaling systems must commit changes.</li>
</ul>
<p>Even metadata operations add up when repeated many times.</p>
<hr>
<h2 id="secure-deletion">Secure Deletion</h2>
<p>Normal delete:</p>
<ul>
<li>Marks space as free</li>
</ul>
<p>Secure delete:</p>
<ul>
<li>Overwrites data blocks</li>
<li>Ensures recovery is impossible</li>
</ul>
<p>On SSDs:</p>
<ul>
<li>Use drive-level secure erase</li>
<li>Or enable full-disk encryption</li>
</ul>
<hr>
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Delete is fast because it removes metadata, not actual data.</li>
<li>Copy is slow because it transfers real bytes.</li>
<li>Move is fast only within the same filesystem.</li>
<li>Deleted files remain until overwritten.</li>
<li>SSDs and HDDs behave differently.</li>
<li>Secure deletion requires special handling.</li>
</ul>
<hr>
<h2 id="what-to-explore-next">What to explore next</h2>
<p>File operations look simple from the UI, but under the surface the OS is managing metadata, block allocation, caching, journaling, and hardware coordination.</p>
<p>If this interested you, here are some rabbit holes worth going down:</p>
<ul>
<li><strong><code>strace</code> on Linux</strong> — run <code>strace rm file.txt</code> or <code>strace cp src dst</code> to see the actual system calls involved</li>
<li><strong>Filesystem internals</strong> — look into how ext4 journaling works, or how ZFS and Btrfs use copy-on-write to make snapshots nearly free</li>
<li><strong>Flash Translation Layer</strong> — how SSDs remap logical blocks to physical NAND pages, and why TRIM exists</li>
<li><strong>Data recovery tools</strong> — tools like <code>testdisk</code> and <code>photorec</code> exploit the fact that "deleted" data is still on disk</li>
</ul>
<p>The next time a file deletes instantly, you'll know — it wasn't magic. It was just a metadata update.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/How-Files-Are-Stored-Deleted-and-Copied-Inside-Your-Computer</link>
        <guid>https://blog.ratnesh-maurya.com/blog/How-Files-Are-Stored-Deleted-and-Copied-Inside-Your-Computer</guid>
        <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>System Design</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/How-Files-Are-Stored-Deleted-and-Copied-Inside-Your-Computer.jpg" length="662600" type="image/jpeg" />
      </item>
      <item>
        <title><![CDATA[The Mechanics of Compression: How 100GB Becomes 25GB]]></title>
        <description><![CDATA[A deep dive into the mathematical 'magic' behind data compression, from Huffman Coding to the architectural differences between ZIP and TAR.GZ.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/The-Mechanics-of-Compression-How-100GB-Becomes-25GB.png" alt="The Mechanics of Compression: How 100GB Becomes 25GB" /></p><h2 id="the-mechanics-of-compression-how-100gb-becomes-25gb">The Mechanics of Compression: How 100GB Becomes 25GB</h2>
<p>In an era of 4K streaming and massive cloud databases, we often take for granted that a 100GB backup can be shrunk down to 25GB. It isn’t magic—and it isn’t just “squeezing” bits. It’s a careful process of spotting patterns, removing redundancy, and using mathematical shorthand so that the same information takes less space. This post explains how that works in plain terms, so you can understand what happens under the hood when you zip a folder or stream a video.</p>
<hr>
<h2 id="1-the-core-idea-redundancy-is-waste">1. The Core Idea: Redundancy Is Waste</h2>
<p>At its heart, compression is about <strong>efficiency</strong>. Most data is repetitive. The same words, phrases, or pixel patterns show up again and again. If the computer has already seen a pattern once, it doesn’t need to store it again in full—it can refer back to it or describe it in fewer bits.</p>
<p>Think of a recipe that says “add salt” five times. You could write “add salt” five times, or you could write “add salt (×5).” The second version carries the same information in less space. Compression algorithms do something similar: they find repetition and describe it more compactly.</p>
<p>So the main job of any compression scheme is to <strong>find redundancy</strong> and <strong>encode it in fewer bits</strong> without losing the ability to recover the original data when needed.</p>
<h3 id="a-real-life-example-compressing-a-book-with-repeated-words">A Real-Life Example: Compressing a Book with Repeated Words</h3>
<p>Imagine a <strong>book</strong> where the same words appear again and again. Words like "the," "and," and "to" might show up thousands of times. Instead of writing the full word every time, we could agree on a <strong>legend</strong> at the start and replace each repeated word with a short <strong>sub-symbol</strong>. Anyone with the legend can reconstruct the original text exactly.</p>
<p>Suppose we define a small dictionary at the top of the page:</p>
<pre><code class="hljs language-text">Legend:  @ = the   |   # = and   |   % = to   |   § = of
</code></pre>
<p>Now take a normal sentence and "compress" it by substituting:</p>
<p><strong>Original (longer):</strong>
"The king and the queen went to the castle and stayed there for the rest of the day."</p>
<p><strong>Compressed (shorter):</strong>
"@ king # @ queen went % @ castle # stayed there for @ rest § @ day."</p>
<p>We still have the same meaning, but we used one character (@, #, %, §) wherever a repeated word appeared. The <strong>legend</strong> is the "dictionary"—it has to be stored or sent once so the reader can decode. For a whole book, if "the" appears 2,000 times, we save (3 − 1) × 2,000 = 4,000 characters just on that one word. Add "and," "to," "of," and other frequent words, and the book shrinks noticeably without losing a single word.</p>
<p>This is exactly the idea behind <strong>dictionary coding</strong> and <strong>Huffman-style</strong> compression: find what repeats, assign it a short code, and use that code everywhere. Real compressors do this automatically; they don't need a human to write the legend.</p>
<p><strong>A real experiment from the wild:</strong> Researchers applied this idea to the full text of <strong>"Alice's Adventures in Wonderland"</strong> by Lewis Carroll. They treated each <em>word</em> as a symbol, counted how often it appeared, and assigned <strong>shorter codes to the most frequent words</strong> (like "the," "and," "to," "a") and <strong>longer codes to rare words</strong> (like "wretched," "yawned"). The result: the text went from <strong>164 KB down to 109 KB</strong>—about <strong>one-third smaller</strong>—with no information lost (<a href="https://www.nayuki.io/page/huffman-coding-english-words"><em>Huffman-coding English words</em></a>, Project Nayuki—includes original text from Project Gutenberg, input/output file sizes, and codebook samples). The same technique—replace repeated words with shorter symbols, and keep a mapping so we can decode—is what powers the compression we use every day in ZIP files and beyond.</p>
<hr>
<h2 id="2-lossless-vs-lossy-two-different-goals">2. Lossless vs. Lossy: Two Different Goals</h2>
<p>Before diving into algorithms, it helps to know that compression splits into two families: <strong>lossless</strong> and <strong>lossy</strong>. The choice depends on whether you can afford to lose any information.</p>
<h3 id="lossless-compression-every-bit-preserved">Lossless Compression: Every Bit Preserved</h3>
<p><strong>Lossless</strong> compression means that after you decompress, you get back <strong>exactly</strong> the original data—every bit. Nothing is discarded.</p>
<ul>
<li><strong>How it works:</strong> The algorithm finds statistical redundancy (repeated or predictable patterns) and encodes them more efficiently. Decompression reverses the process and reconstructs the original stream.</li>
<li><strong>When to use it:</strong> Whenever you cannot afford to lose information—source code, text files, databases, executables, legal documents, medical data, or images where every pixel must stay exact (e.g. PNG for graphics, FLAC for audio).</li>
<li><strong>Common formats:</strong> ZIP, GZIP, PNG, GIF (for lossless use), FLAC.</li>
</ul>
<p>So when you compress 100GB of source code or logs to 25GB with a lossless tool, you can later decompress and get back the same 100GB bit-for-bit.</p>
<h3 id="lossy-compression-smaller-files-imperceptible-loss">Lossy Compression: Smaller Files, Imperceptible Loss</h3>
<p><strong>Lossy</strong> compression <strong>discards</strong> some information on purpose. What you get back is an <strong>approximation</strong> of the original—close enough for human perception, but not identical.</p>
<ul>
<li><strong>How it works:</strong> The algorithm keeps what matters for the intended use (e.g. what the eye or ear notices) and drops “invisible” or less important detail—e.g. colors we can’t easily tell apart, or sounds masked by louder ones.</li>
<li><strong>When to use it:</strong> Media where small quality loss is acceptable—photos (JPEG), music (MP3, AAC), video (H.264, VP9). Streaming and storage would be far heavier without lossy compression.</li>
<li><strong>Common formats:</strong> JPEG, MP3, AAC, H.264, VP9.</li>
</ul>
<p>For the rest of this post we focus on <strong>lossless</strong> compression—the kind that powers ZIP, GZIP, and the 100GB→25GB backup scenario—and then briefly touch how <strong>video</strong> combines similar ideas with lossy temporal compression.</p>
<hr>
<h2 id="3-the-building-blocks-huffman-coding-and-lz77">3. The Building Blocks: Huffman Coding and LZ77</h2>
<p>Most modern lossless compressors (including the ones inside ZIP and GZIP) combine two ideas:</p>
<ol>
<li><strong>Dictionary / back-reference coding</strong> (e.g. LZ77): “I’ve seen this phrase before; refer back to it.”</li>
<li><strong>Entropy coding</strong> (e.g. Huffman): “Frequent symbols get short codes; rare symbols get longer codes.”</li>
</ol>
<p>Together they form algorithms like <strong>DEFLATE</strong> (used in ZIP and GZIP): first reduce repetition with something like LZ77, then shorten the remaining stream with Huffman (or similar) coding.</p>
<hr>
<h3 id="31-huffman-coding-short-codes-for-common-symbols">3.1 Huffman Coding: Short Codes for Common Symbols</h3>
<p>In normal text encoding (e.g. ASCII or UTF-8), every character uses the <strong>same</strong> number of bits (e.g. 8 bits per character). So the letter “E” and the letter “Z” both cost 8 bits, even though “E” appears far more often in English text. That’s wasteful: we’re spending the same budget on rare and common symbols.</p>
<p><strong>Huffman coding</strong> fixes that by giving <strong>variable-length</strong> codes: frequent symbols get <strong>shorter</strong> codes, rare symbols get <strong>longer</strong> codes. On average, the whole message uses fewer bits.</p>
<p>One crucial constraint: no code is allowed to be a <strong>prefix</strong> of another. So if “E” is encoded as <code>10</code>, then no other character’s code can start with <code>10</code> (e.g. no <code>100</code> or <code>101</code>). That way, when we read the compressed stream left-to-right, we always know where one symbol ends and the next begins—no need for extra separators (unlike Morse code, which needs pauses between letters).</p>
<p>A simple way to build such codes is with a <strong>binary tree</strong>: each character is a leaf, and the path from the root to that leaf (e.g. left=0, right=1) is its code. The algorithm builds the tree from the <strong>bottom up</strong>, repeatedly grouping the two least frequent symbols (or groups) until everything is in one tree. Frequent characters end up near the root (short path); rare ones end up deeper (long path).</p>
<p>Conceptually:</p>
<pre><code class="hljs language-text">// Standard 8-bit encoding (e.g. ASCII): every character uses 8 bits
"E" = 01000101 (8 bits)
"Z" = 01011010 (8 bits)

// Huffman encoding (example): frequent letters get shorter codes
"E" = 10      (2 bits)   ← common in English
"Z" = 111010  (6 bits)   ← rare, so longer code
</code></pre>
<p>So Huffman doesn’t “invent” new information; it <strong>represents</strong> the same information in fewer bits by exploiting how often each symbol appears.</p>
<hr>
<h3 id="32-lz77-ive-seen-this-beforepoint-back-to-it">3.2 LZ77: “I’ve Seen This Before—Point Back to It”</h3>
<p>Huffman only shortens symbols based on frequency. It doesn’t remove <strong>repeated phrases</strong>. That’s where <strong>LZ77</strong> (and family) comes in.</p>
<p>LZ77 keeps a <strong>sliding window</strong> of recently seen data. As it reads the input, it looks for the <strong>longest match</strong> between:</p>
<ul>
<li>what’s coming next (the “look-ahead” buffer), and</li>
<li>what it has already seen (the “search” buffer).</li>
</ul>
<p>When it finds a match, it doesn’t output the phrase again. Instead it outputs a <strong>back-reference</strong>: a pair <strong>(distance, length)</strong> meaning “go back <code>distance</code> bytes and copy <code>length</code> bytes.” The decoder can then reconstruct the phrase from earlier in the stream.</p>
<p>So the stream becomes a mix of:</p>
<ul>
<li><strong>Literal</strong> bytes (when there’s no useful match), and</li>
<li><strong>Back-references</strong> (when a phrase repeats).</li>
</ul>
<p>Example:</p>
<pre><code class="hljs language-text">Input:  "The quick brown fox jumps over the quick dog"
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        "the quick" appears twice

Compressed idea:
  Literals: "The quick brown fox jumps over "
  Back-reference: (distance=31, length=9)  → copy 9 bytes from 31 bytes ago
  Literals: " dog"
</code></pre>
<p>After this step, the stream has fewer bytes but still contains the same information. Then Huffman (or similar) is applied to that stream to shorten the representation of literals and back-reference pairs—that’s DEFLATE in a nutshell.</p>
<hr>
<h2 id="4-zip-vs-targz-same-ideas-different-packaging">4. ZIP vs. TAR.GZ: Same Ideas, Different Packaging</h2>
<p>Both ZIP and TAR.GZ use lossless compression (often DEFLATE/GZIP-style), but they <strong>package</strong> files differently. That difference affects compression ratio and how you can access files.</p>
<h3 id="zip-one-file-at-a-time">ZIP: One File at a Time</h3>
<p>ZIP compresses <strong>each file separately</strong> and then puts them in one archive, with a central directory so you can list and extract individual files.</p>
<ul>
<li><strong>Pros:</strong> <strong>Random access.</strong> You can extract <code>image_402.jpg</code> from a 50GB archive without reading or decompressing the rest. Good for browsing and pulling single files.</li>
<li><strong>Cons:</strong> The compressor never looks <strong>across</strong> file boundaries. If 100 files are nearly identical, ZIP may store 100 separate compressed versions and miss shared patterns. So total size can be larger than a “solid” archive.</li>
</ul>
<p>So ZIP is great when you need <strong>per-file access</strong> and broad compatibility (e.g. Windows, macOS, Linux).</p>
<h3 id="targz-one-big-stream-solid-archive">TAR.GZ: One Big Stream (Solid Archive)</h3>
<p>TAR.GZ is a <strong>two-step</strong> process:</p>
<ol>
<li><strong>TAR (Tape Archiver):</strong> Concatenate all files into one continuous <strong>stream</strong> (no compression yet). This is a “solid” archive—one long byte stream.</li>
<li><strong>GZIP:</strong> Compress that <strong>entire</strong> stream with DEFLATE (LZ77 + Huffman) as if it were a single file.</li>
</ol>
<p>Because GZIP sees the whole project as one stream, it can find patterns that span <strong>many files</strong>. A repeated header in <code>file_A.txt</code> can be used to compress the same header in <code>file_Z.txt</code>. That’s why you can get a <strong>much</strong> better ratio—e.g. 100GB down to 25GB—when you have lots of similar or repeated content across files.</p>
<p>Trade-off: to get <strong>any</strong> file out of a TAR.GZ, you typically have to decompress from the beginning up to that file. There’s no cheap “jump to file 402” as in ZIP.</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Step 1: Bundle everything into one stream (size unchanged)</span>
tar -cvf backup.tar ./my_project

<span class="hljs-comment"># Step 2: Compress that whole stream</span>
gzip backup.tar

<span class="hljs-comment"># Result: backup.tar.gz — one compressed stream</span>
</code></pre>
<p><strong>When to use TAR.GZ:</strong> Backups, source tarballs, logs, or any case where you want <strong>maximum compression</strong> and usually extract the whole archive (or don’t mind decompressing from the start to reach a file). Common on Linux and in DevOps pipelines.</p>
<p><strong>When to prefer ZIP:</strong> When you need <strong>random access</strong> to individual files or maximum compatibility with non-Unix systems.</p>
<hr>
<h2 id="5-how-video-compression-goes-further-temporal-redundancy">5. How Video Compression Goes Further: Temporal Redundancy</h2>
<p>Video has a special kind of redundancy: <strong>between frames</strong>, most of the image doesn’t change. Only moving parts (and lighting changes) differ. So instead of storing every frame in full, modern codecs (e.g. H.264) use <strong>temporal compression</strong>: store one full frame, then for the next frames store mainly <strong>what changed</strong>.</p>
<p>Roughly:</p>
<ul>
<li><strong>I-frames (Intra-frames):</strong> Full frames—like a JPEG image. They don’t depend on other frames. Used as anchors so you can seek or recover from errors.</li>
<li><strong>P-frames (Predictive):</strong> Encoded as “differences” from a previous frame (or I-frame). Much smaller than a full frame.</li>
<li><strong>B-frames (Bi-predictive):</strong> Use both past and future frames to predict the current frame, so they can be even smaller.</li>
</ul>
<p>So conceptually:</p>
<pre><code class="hljs language-text">Frame 1 (I):  Full image of a sunset
Frame 2 (P):  “Same as Frame 1, but the bird moved 2 pixels left”
Frame 3 (P):  “Same as Frame 2, but the bird moved 2 pixels left”
…
Total stored: one full frame + many small “deltas” and motion vectors
</code></pre>
<p><img src="https://blog.ratnesh-maurya.com/images/blogs-inline-image/inter-frame-prediction-video-compression.png" alt="Inter-frame prediction: I-frame and P-frames with motion vectors. Frame 1 is a full photo; Frames 2 and 3 store only the moving bird and motion vector (-2, 0)."></p>
<p>That’s <strong>temporal</strong> compression. Video also uses <strong>spatial</strong> compression inside each frame (similar in spirit to JPEG). Together, temporal + spatial (and often lossy choices) let streaming and storage stay practical at 1080p and 4K.</p>
<hr>
<h2 id="6-wrapping-up">6. Wrapping Up</h2>
<p>Compression is the invisible engine behind smaller backups, faster transfers, and watchable video. The main ideas are:</p>
<ul>
<li><strong>Redundancy</strong> is removed or shortened: repeated phrases become back-references (LZ77), and frequent symbols get short codes (Huffman).</li>
<li><strong>Lossless</strong> (ZIP, GZIP, PNG) keeps every bit; <strong>lossy</strong> (JPEG, MP3, H.264) trades a bit of quality for much smaller size.</li>
<li><strong>ZIP</strong> compresses file-by-file for random access; <strong>TAR.GZ</strong> compresses one solid stream for better ratio when you don’t need per-file random access.</li>
<li><strong>Video</strong> adds <strong>temporal</strong> compression: store full frames rarely, and mostly store changes between frames.</li>
</ul>
<p>Understanding these mechanics helps you choose the right format—whether you’re optimizing Next.js assets, shipping backups to S3, or tuning video encoding—and demystifies how 100GB can become 25GB without losing a single bit.</p>
<blockquote>
<p>written using AI tools</p>
</blockquote>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/The-Mechanics-of-Compression-How-100GB-Becomes-25GB</link>
        <guid>https://blog.ratnesh-maurya.com/blog/The-Mechanics-of-Compression-How-100GB-Becomes-25GB</guid>
        <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>Computer Science</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/The-Mechanics-of-Compression-How-100GB-Becomes-25GB.png" length="1619669" type="image/png" />
      </item>
      <item>
        <title><![CDATA[Building This Blog: A Modern Next.js Blog with Markdown]]></title>
        <description><![CDATA[How I built this blog using Next.js, TypeScript, Tailwind CSS, and markdown for content management. A complete guide to creating a fast, SEO-optimized blog.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/building-blog.jpg" alt="Building This Blog: A Modern Next.js Blog with Markdown" /></p><h1>Building This Blog: an MDX‑powered Next.js blog</h1>
<p>This blog is a small, opinionated setup for writing long‑form content that feels good to read and fun to build on.</p>
<p>Under the hood it uses static generation, MDX, and a theme‑aware UI that works in both light and dark modes.</p>
<p>Everything after the frontmatter is pure <strong>MDX</strong>. Components like <code>Callout</code>, <code>Steps</code>, architecture diagrams, flows, and charts are all React components rendered directly in the post body.</p>
<h2>Stack I ended up with</h2>
<p>I wanted something fast, simple, and easy to reason about over years—not weeks.</p>
<pre><code>The site is a static App Router app. Routes like `/blog/[slug]`, `/til/[slug]`, `/technical-terms/[slug]` are statically generated at build time.


TypeScript keeps the content layer and components honest. Tailwind + CSS variables handle layout and theming.


All long‑form content lives in the repo inside content/, written as `.md` or `.mdx` files with frontmatter.


Views, upvotes, and UTM events are stored in Supabase and surfaced through a custom analytics dashboard.
</code></pre>
<h2>How content works</h2>
<p>All posts are simple files in the repo—no CMS required.</p>
<pre><code class="language-markdown">---
title: "Your Post Title"
description: "Post description"
date: "2024-01-20"
author: "Your Name"
tags: ["tag1", "tag2"]
category: "Category"
featured: true
---

Your content here...
</code></pre>
<ul>
<li><strong>Markdown (<code>.md</code>)</strong> posts go through a Remark pipeline and are rendered as HTML.</li>
<li><strong>MDX (<code>.mdx</code>)</strong> posts are compiled at runtime in the page using <code>next-mdx-remote/rsc</code>, with a shared component map.</li>
</ul>
<p>For posts that need diagrams, flows, or charts, I use <code>.mdx</code> and drop in components like ArchitectureCard, FlowStep, and DemoBarChart.</p>
<h3>Authoring flow</h3>
<pre><code>Add a new `.md` or `.mdx` file to content/blog/ with frontmatter for SEO, tags, and images.


Run npm run dev and iterate on copy, diagrams, and layout until the post feels polished.


Push to the main branch. The build step generates static HTML for every route and updates search data + OG images.
</code></pre>
<h2>Architecture of the blog</h2>
<p>At a high level, the blog is just three pieces: browser, Next.js, and Supabase.</p>
<pre><code>Readers get fully rendered HTML from the CDN, with minimal JavaScript on top for upvotes, analytics, and small interactions.


Next.js statically generates pages for blog posts, TIL entries, technical terms, and lists. It also exposes a few API routes for analytics.


Supabase stores per‑page views, upvotes, and UTM events that feed the analytics dashboard and charts.
</code></pre>
<h2>Request–response flow for a page view</h2>
<p>Here’s how a single blog page view flows through the system.</p>
<pre><code>The CDN serves a pre‑rendered HTML page that was generated at build time.


Next.js hydrates the page; components like the upvote button, custom cursor, and view counter start working.


A lightweight request records the view / upvote in Supabase without blocking the reader.


Aggregated stats are used to power Recharts visualizations on the analytics page.
</code></pre>
<h2>Visualizing traffic with charts</h2>
<p>The same Recharts setup used in the analytics dashboard is available inside MDX posts.</p>
<p>Charts are plain React components exposed to MDX. They respect the global theme and use the same accent palette as the rest of the site.</p>
<h2>Performance before and after</h2>
<p>Static export plus small tweaks around assets made a noticeable difference in user‑facing numbers.</p>
<h2>Why this setup works for me</h2>
<ol>
<li><strong>Files as the source of truth</strong> – I can write posts in a text editor, version them in git, and refactor them like any other code.</li>
<li><strong>Static by default</strong> – Most pages are static HTML, which is great for speed and reliability.</li>
<li><strong>MDX when I need power</strong> – Architecture diagrams, flows, and charts are just components; I only reach for them when a post really needs them.</li>
<li><strong>Theme‑aware UI</strong> – Components use the same CSS variables as the rest of the app, so they look good in both light and dark modes.</li>
</ol>
<p>If you want to explore the implementation details, the full source code is on <a href="https://github.com/ratnesh-maurya/blog.ratnesh-maurya.com">GitHub</a>. You could clone it, point it at your own content folder, and have a similar blog running in minutes.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/building-this-blog</link>
        <guid>https://blog.ratnesh-maurya.com/blog/building-this-blog</guid>
        <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>Web Development</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/building-blog.jpg" length="48365" type="image/jpeg" />
      </item>
      <item>
        <title><![CDATA[Optimizing Memory Layout in Go: A Deep Dive into Struct Design]]></title>
        <description><![CDATA[How Go struct field ordering affects memory via alignment and padding, with benchmarks across millions of allocations and practical tools to detect wasted space.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/Optimizing-Memory-Layout-in-Go-A-Deep-Dive-into-Struct-Design.jpg" alt="Optimizing Memory Layout in Go: A Deep Dive into Struct Design" /></p><p>Reorder the fields in a Go struct and the size changes — without changing the data it holds. A <code>bool</code> next to an <code>int64</code> wastes 7 bytes of padding. Across 10 million allocations, that's 67MB of memory you're paying for but never using.</p>
<p>This matters in high-throughput services where struct slices dominate heap usage: event pipelines, in-memory caches, analytics collectors.</p>
<h2 id="how-alignment-and-padding-work">How alignment and padding work</h2>
<p>Go stores struct fields in a contiguous block of memory. Each field must be aligned to a memory address that's a multiple of its own size — <code>int64</code> aligns to 8 bytes, <code>int32</code> to 4, <code>bool</code> to 1. When a smaller field is followed by a larger one, the compiler inserts invisible padding bytes to satisfy the alignment requirement.</p>
<pre><code class="hljs language-go"><span class="hljs-keyword">type</span> Bad <span class="hljs-keyword">struct</span> {
    Active  <span class="hljs-type">bool</span>    <span class="hljs-comment">// 1 byte</span>
    <span class="hljs-comment">// 7 bytes padding</span>
    Balance <span class="hljs-type">float64</span> <span class="hljs-comment">// 8 bytes</span>
    Age     <span class="hljs-type">uint8</span>   <span class="hljs-comment">// 1 byte</span>
    <span class="hljs-comment">// 7 bytes padding</span>
}
<span class="hljs-comment">// Total: 24 bytes (only 10 bytes of actual data)</span>
</code></pre>
<p>Reorder from largest to smallest:</p>
<pre><code class="hljs language-go"><span class="hljs-keyword">type</span> Good <span class="hljs-keyword">struct</span> {
    Balance <span class="hljs-type">float64</span> <span class="hljs-comment">// 8 bytes</span>
    Active  <span class="hljs-type">bool</span>    <span class="hljs-comment">// 1 byte</span>
    Age     <span class="hljs-type">uint8</span>   <span class="hljs-comment">// 1 byte</span>
    <span class="hljs-comment">// 6 bytes padding (struct itself aligns to 8)</span>
}
<span class="hljs-comment">// Total: 16 bytes (same 10 bytes of data, 8 bytes less waste)</span>
</code></pre>
<p>That's a 33% reduction per struct, just by reordering fields.</p>
<h2 id="measuring-the-difference">Measuring the difference</h2>
<p>Use <code>reflect.TypeOf</code> and <code>unsafe.Sizeof</code> to check struct sizes at runtime:</p>
<pre><code class="hljs language-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"reflect"</span>
    <span class="hljs-string">"unsafe"</span>
)

<span class="hljs-keyword">type</span> Bad <span class="hljs-keyword">struct</span> {
    Active  <span class="hljs-type">bool</span>
    Balance <span class="hljs-type">float64</span>
    Age     <span class="hljs-type">uint8</span>
}

<span class="hljs-keyword">type</span> Good <span class="hljs-keyword">struct</span> {
    Balance <span class="hljs-type">float64</span>
    Active  <span class="hljs-type">bool</span>
    Age     <span class="hljs-type">uint8</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    fmt.Println(<span class="hljs-string">"Bad:"</span>, unsafe.Sizeof(Bad{}), <span class="hljs-string">"bytes"</span>)   <span class="hljs-comment">// 24</span>
    fmt.Println(<span class="hljs-string">"Good:"</span>, unsafe.Sizeof(Good{}), <span class="hljs-string">"bytes"</span>) <span class="hljs-comment">// 16</span>

    <span class="hljs-comment">// Field-by-field inspection</span>
    t := reflect.TypeOf(Bad{})
    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &#x3C; t.NumField(); i++ {
        f := t.Field(i)
        fmt.Printf(<span class="hljs-string">"  %s: size=%d, offset=%d\n"</span>, f.Name, f.Type.Size(), f.Offset)
    }
}
</code></pre>
<h2 id="how-much-memory-this-saves-at-scale">How much memory this saves at scale</h2>
<p>Here's the math for a real scenario — an analytics service tracking page view events:</p>
<pre><code class="hljs language-go"><span class="hljs-keyword">type</span> PageView <span class="hljs-keyword">struct</span> {
    <span class="hljs-comment">// Unoptimized layout</span>
    IsBot      <span class="hljs-type">bool</span>      <span class="hljs-comment">// 1 + 7 padding</span>
    Timestamp  <span class="hljs-type">int64</span>     <span class="hljs-comment">// 8</span>
    StatusCode <span class="hljs-type">uint16</span>    <span class="hljs-comment">// 2 + 6 padding</span>
    Duration   <span class="hljs-type">int64</span>     <span class="hljs-comment">// 8</span>
    UserID     <span class="hljs-type">uint32</span>    <span class="hljs-comment">// 4 + 4 padding</span>
    PathHash   <span class="hljs-type">uint64</span>    <span class="hljs-comment">// 8</span>
}
<span class="hljs-comment">// Size: 48 bytes</span>

<span class="hljs-keyword">type</span> PageViewOptimized <span class="hljs-keyword">struct</span> {
    <span class="hljs-comment">// Sorted by alignment: 8 → 4 → 2 → 1</span>
    Timestamp  <span class="hljs-type">int64</span>
    Duration   <span class="hljs-type">int64</span>
    PathHash   <span class="hljs-type">uint64</span>
    UserID     <span class="hljs-type">uint32</span>
    StatusCode <span class="hljs-type">uint16</span>
    IsBot      <span class="hljs-type">bool</span>
}
<span class="hljs-comment">// Size: 32 bytes</span>
</code></pre>
<table>
<thead>
<tr>
<th>Struct count</th>
<th>Unoptimized</th>
<th>Optimized</th>
<th>Saved</th>
</tr>
</thead>
<tbody>
<tr>
<td>100K</td>
<td>4.6 MB</td>
<td>3.1 MB</td>
<td>1.5 MB</td>
</tr>
<tr>
<td>1M</td>
<td>45.8 MB</td>
<td>30.5 MB</td>
<td>15.3 MB</td>
</tr>
<tr>
<td>10M</td>
<td>457 MB</td>
<td>305 MB</td>
<td>152 MB</td>
</tr>
</tbody>
</table>
<p>At 10 million structs, the difference is 152MB — enough to matter for your container memory limits and GC pressure.</p>
<h2 id="automated-detection-with-fieldalignment">Automated detection with fieldalignment</h2>
<p>You don't need to manually audit every struct. The <code>fieldalignment</code> analyzer from <code>golang.org/x/tools</code> catches suboptimal layouts automatically:</p>
<pre><code class="hljs language-bash">go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest
fieldalignment ./...
</code></pre>
<p>It reports every struct that could be smaller and suggests the optimal field order. You can also run it as part of <code>golangci-lint</code> by enabling the <code>govet</code> linter with the <code>fieldalignment</code> check.</p>
<h2 id="the-alignment-rules">The alignment rules</h2>
<table>
<thead>
<tr>
<th>Type</th>
<th>Size</th>
<th>Alignment</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>bool</code>, <code>byte</code>, <code>uint8</code>, <code>int8</code></td>
<td>1 byte</td>
<td>1</td>
</tr>
<tr>
<td><code>uint16</code>, <code>int16</code></td>
<td>2 bytes</td>
<td>2</td>
</tr>
<tr>
<td><code>uint32</code>, <code>int32</code>, <code>float32</code></td>
<td>4 bytes</td>
<td>4</td>
</tr>
<tr>
<td><code>uint64</code>, <code>int64</code>, <code>float64</code>, pointer, <code>string</code>, slice, map, interface</td>
<td>8 bytes</td>
<td>8</td>
</tr>
</tbody>
</table>
<p>The general rule: <strong>sort fields from largest alignment to smallest.</strong> This minimizes padding because smaller fields can pack together in the leftover space after larger fields.</p>
<p>Structs themselves are padded to a multiple of their largest field's alignment. That's why the <code>Good</code> struct above is 16 bytes (multiple of 8) even though the data only needs 10 bytes.</p>
<h2 id="when-not-to-bother">When not to bother</h2>
<p>Field ordering optimization is worth the effort when:</p>
<ul>
<li>You allocate millions of the same struct (event pipelines, time-series data, game state)</li>
<li>The struct is stored in a large slice that stays in memory</li>
<li>You're hitting container memory limits or seeing heavy GC pauses</li>
</ul>
<p>It's not worth the effort when:</p>
<ul>
<li>The struct is allocated once or a handful of times</li>
<li>Readability would suffer from rearranging logically grouped fields</li>
<li>The struct is mostly pointers and strings (already 8-byte aligned)</li>
</ul>
<p>Run <code>fieldalignment</code> on your codebase as a CI check. Fix the easy wins — the structs that save 8+ bytes per instance — and leave the rest alone. The tool does the thinking for you.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/Optimizing-Memory-Layout-in-Go-A-Deep-Dive-into-Struct-Design</link>
        <guid>https://blog.ratnesh-maurya.com/blog/Optimizing-Memory-Layout-in-Go-A-Deep-Dive-into-Struct-Design</guid>
        <pubDate>Fri, 10 Jan 2025 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>Golang</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/Optimizing-Memory-Layout-in-Go-A-Deep-Dive-into-Struct-Design.jpg" length="113396" type="image/jpeg" />
      </item>
      <item>
        <title><![CDATA[Deploy a Nanoc Static Site to S3 with GitHub Actions]]></title>
        <description><![CDATA[A step-by-step guide to automating Nanoc website deployment to AWS S3 using GitHub Actions, including S3 bucket policy, IAM setup, and the complete workflow YAML.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/Easily-Deploy-Your-Nanoc-Website-to-S3-with-GitHub-Actions.jpg" alt="Deploy a Nanoc Static Site to S3 with GitHub Actions" /></p><p>Deploying a static site to S3 manually means running <code>aws s3 sync</code> from your laptop every time you make a change. That gets old fast. GitHub Actions can automate the entire flow: push to <code>main</code>, and the site builds and deploys itself.</p>
<p>This guide walks through the full setup for a <a href="https://nanoc.app/">Nanoc</a> site, but the S3 + GitHub Actions pattern works for any static site generator.</p>
<p><a href="https://github.com/ratnesh-maurya/365-Days-of-DevOps/tree/main">Source code on GitHub</a></p>
<h2 id="what-you-need-before-starting">What you need before starting</h2>
<ul>
<li>An <strong>AWS account</strong> with an S3 bucket configured for static website hosting</li>
<li>An <strong>IAM user</strong> with scoped permissions (created in the steps below)</li>
<li>A <strong>GitHub repository</strong> containing your Nanoc source code</li>
</ul>
<h2 id="step-1-configure-the-s3-bucket-policy">Step 1: Configure the S3 bucket policy</h2>
<p>Your bucket needs a policy that allows public read access to its contents. Apply this to the bucket in the S3 console under Permissions > Bucket Policy:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"Version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2008-10-17"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"Id"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"PolicyForPublicWebsiteContent"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"Statement"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
    <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"Sid"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"PublicReadGetObject"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Allow"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Principal"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-attr">"AWS"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"*"</span>
      <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"s3:GetObject"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:s3:::docsite-github-action/*"</span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>Replace <code>docsite-github-action</code> with your actual bucket name.</p>
<h2 id="step-2-create-a-scoped-iam-user">Step 2: Create a scoped IAM user</h2>
<p>Create a dedicated IAM user for GitHub Actions — don't use your root credentials. Attach this policy, which gives the minimum permissions needed for <code>s3 sync</code>:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"Version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2012-10-17"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"Statement"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
    <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"Sid"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"AccessToGetBucketLocation"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Allow"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"s3:GetBucketLocation"</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"arn:aws:s3:::*"</span><span class="hljs-punctuation">]</span>
    <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
    <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"Sid"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"AccessToWebsiteBuckets"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Allow"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
        <span class="hljs-string">"s3:PutObject"</span><span class="hljs-punctuation">,</span>
        <span class="hljs-string">"s3:PutObjectAcl"</span><span class="hljs-punctuation">,</span>
        <span class="hljs-string">"s3:GetObject"</span><span class="hljs-punctuation">,</span>
        <span class="hljs-string">"s3:ListBucket"</span><span class="hljs-punctuation">,</span>
        <span class="hljs-string">"s3:DeleteObject"</span>
      <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
        <span class="hljs-string">"arn:aws:s3:::docsite-github-action"</span><span class="hljs-punctuation">,</span>
        <span class="hljs-string">"arn:aws:s3:::docsite-github-action/*"</span>
      <span class="hljs-punctuation">]</span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<h2 id="step-3-add-aws-credentials-to-github-secrets">Step 3: Add AWS credentials to GitHub Secrets</h2>
<p>In your GitHub repository, go to <strong>Settings > Secrets and variables > Actions</strong> and add two repository secrets:</p>
<ul>
<li><code>AWS_ACCESS_KEY_ID</code> — your IAM user's access key</li>
<li><code>AWS_SECRET_ACCESS_KEY</code> — the corresponding secret key</li>
</ul>
<p>These are injected into the workflow at runtime. They never appear in logs.</p>
<h2 id="step-4-create-the-github-actions-workflow">Step 4: Create the GitHub Actions workflow</h2>
<p>Create <code>.github/workflows/deploy.yml</code> in your repository:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Nanoc</span> <span class="hljs-string">Compile</span> <span class="hljs-string">and</span> <span class="hljs-string">Upload</span> <span class="hljs-string">to</span> <span class="hljs-string">S3</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">Repository</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Ruby</span> <span class="hljs-string">and</span> <span class="hljs-string">Nanoc</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          sudo apt-get update
          sudo apt-get install -y ruby-full build-essential zlib1g-dev
          sudo gem install bundler
          sudo gem install nanoc
          sudo gem install adsf
          sudo gem install kramdown
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Nanoc</span> <span class="hljs-string">Website</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          ls
          cd tutorial &#x26;&#x26; nanoc
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">AWS</span> <span class="hljs-string">credentials</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">aws-actions/configure-aws-credentials@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">aws-access-key-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">aws-secret-access-key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">aws-region:</span> <span class="hljs-string">ap-south-1</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Push</span> <span class="hljs-string">to</span> <span class="hljs-string">S3</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">aws</span> <span class="hljs-string">s3</span> <span class="hljs-string">sync</span> <span class="hljs-string">tutorial/output/</span> <span class="hljs-string">s3://docsite-github-action</span>
</code></pre>
<p>The workflow triggers on every push to <code>main</code>. It installs Ruby and Nanoc, compiles the site, and syncs the output directory to your S3 bucket.</p>
<h2 id="alternatives-to-consider">Alternatives to consider</h2>
<p>This setup works well for simple static sites, but depending on your needs, other approaches might be a better fit:</p>
<table>
<thead>
<tr>
<th>Approach</th>
<th>When to use it</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>S3 + CloudFront</strong></td>
<td>You need HTTPS and a CDN for global performance</td>
</tr>
<tr>
<td><strong>Vercel / Netlify</strong></td>
<td>You want zero-config deploys with preview URLs for every PR</td>
</tr>
<tr>
<td><strong>GitHub Pages</strong></td>
<td>You don't need AWS and want the simplest possible hosting</td>
</tr>
<tr>
<td><strong>AWS Amplify</strong></td>
<td>You want managed CI/CD with automatic branch deploys on AWS</td>
</tr>
</tbody>
</table>
<p>S3 alone doesn't give you HTTPS — you'd need CloudFront in front of it for that. If HTTPS and preview deployments matter to you, Vercel or Netlify will save you time.</p>
<h2 id="common-pitfalls">Common pitfalls</h2>
<ul>
<li><strong>Forgetting to enable static website hosting</strong> on the S3 bucket — without it, S3 serves files as downloads instead of web pages.</li>
<li><strong>Overly broad IAM permissions</strong> — scope the policy to your specific bucket, not <code>s3:*</code> on <code>*</code>.</li>
<li><strong>Cache invalidation</strong> — <code>s3 sync</code> updates files but doesn't invalidate CloudFront caches. If you add CloudFront later, add <code>aws cloudfront create-invalidation</code> to the workflow.</li>
<li><strong>Region mismatch</strong> — set <code>aws-region</code> in the workflow to match your bucket's region.</li>
</ul>
<p>The complete workflow YAML and policies are in the <a href="https://github.com/ratnesh-maurya/365-Days-of-DevOps/tree/main">repo</a>.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/Easily-Deploy-Your-Nanoc-Website-to-S3-with-GitHub-Actions</link>
        <guid>https://blog.ratnesh-maurya.com/blog/Easily-Deploy-Your-Nanoc-Website-to-S3-with-GitHub-Actions</guid>
        <pubDate>Sat, 23 Nov 2024 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>AWS</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/Easily-Deploy-Your-Nanoc-Website-to-S3-with-GitHub-Actions.jpg" length="91311" type="image/jpeg" />
      </item>
      <item>
        <title><![CDATA[Architectural Design for a Ride App such as OLA, UBER, RAPIDO]]></title>
        <description><![CDATA[Microservices architecture for ride-sharing: service boundaries, REST vs gRPC vs message queues, geo-location tracking, and trade-offs between monolith-first and microservices.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/Architectural-Design-for-a-Ride-App-such-as-OLA-UBER-RAPIDO.jpg" alt="Architectural Design for a Ride App such as OLA, UBER, RAPIDO" /></p><p>A ride-sharing app needs to match riders with drivers in real-time, track locations, process payments, and handle all of this at scale across a city. This is my take on how to architect such a system using microservices — and where a simpler approach might actually be better.</p>
<h2 id="why-microservices-for-a-ride-app">Why microservices for a ride app</h2>
<p>The core challenge is that different parts of the system have very different scaling requirements:</p>
<ul>
<li><strong>Geo-location tracking</strong> needs to handle thousands of location updates per second with sub-100ms latency</li>
<li><strong>Payment processing</strong> needs strong consistency and idempotency but handles far fewer requests</li>
<li><strong>Notifications</strong> are fire-and-forget at high volume</li>
<li><strong>User authentication</strong> is read-heavy with infrequent writes</li>
</ul>
<p>A monolith would force all of these to scale together. Microservices let you scale the geo-location service to 50 instances while keeping the payment service at 3.</p>
<p>That said, if you're building an MVP or serving a single city, start with a monolith. Uber itself started as a monolith. Extract services only when specific components hit scaling bottlenecks.</p>
<h2 id="service-boundaries">Service boundaries</h2>
<p>Each service owns its data and exposes a clear API:</p>
<p><strong>User Service</strong> — Manages rider and driver accounts, authentication (OAuth2/OIDC), profile data, and session management. Backed by PostgreSQL.</p>
<p><strong>Ride Service</strong> — The core coordination service. Handles ride requests, matches riders with nearby available drivers, tracks ride state (requested → matched → in-progress → completed), and calculates fares. Backed by PostgreSQL with Redis for active ride state.</p>
<p><strong>Driver Service</strong> — Manages driver availability, approval status, vehicle information, and earnings. Tracks which drivers are online, idle, or on a ride. Backed by PostgreSQL.</p>
<p><strong>Geo-Location Service</strong> — The highest-throughput service. Receives GPS coordinates from driver and rider apps every 3–5 seconds, stores them in a spatial index (Redis with geospatial commands or a dedicated service like H3), and answers proximity queries ("find the 5 nearest available drivers within 3km"). This is the service that needs the most aggressive scaling.</p>
<p><strong>Payment Service</strong> — Integrates with payment gateways (Razorpay, Stripe). Processes charges after ride completion, handles refunds, and manages driver payouts. Must be idempotent — a network retry should never charge a rider twice.</p>
<p><strong>Notification Service</strong> — Sends push notifications (ride matched, driver arriving, ride completed), SMS fallbacks, and email receipts. Consumes events from a message queue rather than being called directly.</p>
<h2 id="how-the-services-communicate">How the services communicate</h2>
<p>Not all communication should use the same pattern:</p>
<table>
<thead>
<tr>
<th>Pattern</th>
<th>Use case</th>
<th>Why</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>REST/HTTP</strong></td>
<td>Client → API Gateway → Services</td>
<td>Simple request/response for CRUD operations</td>
</tr>
<tr>
<td><strong>gRPC</strong></td>
<td>Service-to-service (e.g., Ride Service → Geo-Location Service)</td>
<td>Low latency, typed contracts, streaming support</td>
</tr>
<tr>
<td><strong>Message queue (Kafka/RabbitMQ)</strong></td>
<td>Ride Service → Notification Service, Ride Service → Analytics</td>
<td>Async, decoupled, no backpressure on the producer</td>
</tr>
</tbody>
</table>
<p>The <strong>API Gateway</strong> sits in front of all services and handles routing, rate limiting, and authentication token validation. Clients never talk directly to internal services.</p>
<p>A critical design decision: the Ride Service publishes a <code>ride.completed</code> event to Kafka. The Payment Service, Notification Service, and Analytics Service all consume this event independently. This means adding a new consumer (say, a driver-rating prompt) doesn't require changing the Ride Service at all.</p>
<h2 id="scaling-and-fault-tolerance">Scaling and fault tolerance</h2>
<p><strong>Load balancing.</strong> An L7 load balancer (like AWS ALB or Envoy) distributes requests across service instances. The Geo-Location Service gets its own load balancer with sticky sessions disabled (since requests are stateless).</p>
<p><strong>Auto-scaling.</strong> Kubernetes Horizontal Pod Autoscaler watches CPU and custom metrics (like queue depth for the Notification Service). The Geo-Location Service might scale from 10 pods at 2 AM to 80 pods at 6 PM during rush hour.</p>
<p><strong>Circuit breakers.</strong> If the Payment Service is down, the Ride Service shouldn't hang waiting for it. A circuit breaker (e.g., via Istio or a library like resilience4j) fails fast after N consecutive errors and falls back to queuing the payment for later processing.</p>
<p><strong>Health checks.</strong> Every service exposes <code>/healthz</code> (liveness) and <code>/readyz</code> (readiness) endpoints. Kubernetes restarts crashed pods automatically and stops routing traffic to pods that aren't ready.</p>
<h2 id="security-considerations">Security considerations</h2>
<ul>
<li><strong>Authentication:</strong> OAuth2 with JWTs for user-facing APIs. Service-to-service calls use mTLS within the Kubernetes cluster.</li>
<li><strong>Data encryption:</strong> AES-256 at rest, TLS 1.3 in transit. Payment card data never touches your services — use the payment gateway's tokenization.</li>
<li><strong>Rate limiting:</strong> Applied at the API Gateway level. Geo-location updates are rate-limited per device to prevent abuse.</li>
<li><strong>Secrets management:</strong> HashiCorp Vault or AWS Secrets Manager. No secrets in environment variables or config files.</li>
</ul>
<h2 id="deployment">Deployment</h2>
<ul>
<li><strong>Containerization:</strong> Each service is a Docker image with a multi-stage build (builder → runtime). Images are scanned for vulnerabilities in CI.</li>
<li><strong>Orchestration:</strong> Kubernetes with namespaces per environment (dev, staging, prod). Services declare resource requests and limits.</li>
<li><strong>CI/CD:</strong> GitHub Actions or GitLab CI runs tests, builds images, pushes to a container registry, and deploys via Helm charts or ArgoCD.</li>
<li><strong>Observability:</strong> Prometheus + Grafana for metrics, Jaeger for distributed tracing, Fluentd/Loki for centralized logs. Every service emits structured JSON logs with a correlation ID.</li>
</ul>
<h2 id="what-id-do-differently-at-different-scales">What I'd do differently at different scales</h2>
<table>
<thead>
<tr>
<th>Scale</th>
<th>Approach</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>MVP (1 city, &#x3C; 1K rides/day)</strong></td>
<td>Monolith with a single PostgreSQL database. Extract the geo-location queries into a background worker if they slow down the main app.</td>
</tr>
<tr>
<td><strong>Growth (5 cities, 10K rides/day)</strong></td>
<td>Extract Geo-Location and Notification as separate services. Keep everything else in the monolith. Add Redis for caching.</td>
</tr>
<tr>
<td><strong>Scale (50+ cities, 100K+ rides/day)</strong></td>
<td>Full microservices as described above. Invest in Kafka for event-driven architecture, dedicated SRE team, and per-service databases.</td>
</tr>
</tbody>
</table>
<p>The biggest mistake is building for the third row when you're at the first row. Start simple, measure, and extract when the pain is real.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/Architectural-Design-for-a-Ride-App-such-as-OLA-UBER-RAPIDO</link>
        <guid>https://blog.ratnesh-maurya.com/blog/Architectural-Design-for-a-Ride-App-such-as-OLA-UBER-RAPIDO</guid>
        <pubDate>Tue, 30 Jul 2024 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>Software Architecture</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/Architectural-Design-for-a-Ride-App-such-as-OLA-UBER-RAPIDO.jpg" length="36695" type="image/jpeg" />
      </item>
      <item>
        <title><![CDATA[Amazon SNS: Cost Reduction and Reliable Delivery for Startups]]></title>
        <description><![CDATA[How Amazon SNS helps startups cut messaging costs with pay-per-message pricing, automatic retries, dead letter queues, and multi-region delivery — and when SQS might be a better fit.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/Amazon-SNS-for-Cost-Reduction-and-Message-Delivery-Assurance-in-Startups.jpg" alt="Amazon SNS: Cost Reduction and Reliable Delivery for Startups" /></p><p>Most startups need to send notifications — order confirmations, alerts, password resets — but don't want to run their own messaging infrastructure. <a href="https://aws.amazon.com/sns/">Amazon Simple Notification Service (SNS)</a> solves this: a fully managed pub/sub service where you pay per message, not per server.</p>
<p>Here's what makes it worth evaluating, where it falls short, and how it compares to alternatives.</p>
<h2 id="the-startup-messaging-problem">The startup messaging problem</h2>
<p>Early-stage teams face a specific tension: they need reliable message delivery across email, SMS, and push notifications, but can't justify the cost or operational overhead of self-hosted messaging systems. The requirements typically include:</p>
<ul>
<li><strong>Pay-as-you-go pricing</strong> — no monthly minimums, no long-term contracts</li>
<li><strong>Multi-channel delivery</strong> — email, SMS, mobile push, HTTP webhooks, Lambda triggers</li>
<li><strong>Automatic retries</strong> — temporary failures shouldn't lose messages</li>
<li><strong>Global reach</strong> — users in multiple regions need low-latency delivery</li>
</ul>
<p>SNS addresses all four. But so do other services, which is why the trade-offs matter.</p>
<h2 id="how-sns-keeps-costs-low">How SNS keeps costs low</h2>
<p>SNS uses a pay-per-message model. The first million SNS API requests per month are free. After that, it's $0.50 per million requests. SMS and email have separate per-message pricing that varies by destination country.</p>
<p>For a startup sending 100K push notifications and 10K emails per month, the SNS cost is effectively zero (within the free tier). Compare that to a dedicated email service like SendGrid or Mailgun, which typically start at $15–20/month for similar volumes.</p>
<p>The catch: SNS doesn't do rich email templates, drip campaigns, or analytics. It's a delivery pipe, not a marketing platform.</p>
<h2 id="how-sns-ensures-delivery">How SNS ensures delivery</h2>
<p>Three mechanisms prevent message loss:</p>
<p><strong>Retry logic.</strong> When a delivery attempt fails (subscriber endpoint is down, network timeout), SNS automatically retries with exponential backoff. The retry policy is configurable per delivery protocol.</p>
<p><strong>Dead letter queues.</strong> Messages that exhaust all retry attempts are routed to an SQS dead letter queue instead of being silently dropped. You can inspect these later, replay them, or trigger alerts.</p>
<p><strong>Cross-AZ replication.</strong> Messages are replicated across multiple availability zones within a region before SNS acknowledges the publish request. This protects against hardware failures in a single data center.</p>
<h2 id="sns-vs-sqs-vs-eventbridge-when-to-use-what">SNS vs SQS vs EventBridge: when to use what</h2>
<p>This is the decision most teams get wrong. All three are AWS messaging services, but they solve different problems:</p>
<table>
<thead>
<tr>
<th>Service</th>
<th>Pattern</th>
<th>Best for</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SNS</strong></td>
<td>Pub/sub (fan-out)</td>
<td>Broadcasting one event to many subscribers</td>
</tr>
<tr>
<td><strong>SQS</strong></td>
<td>Point-to-point queue</td>
<td>Decoupling a producer from a single consumer</td>
</tr>
<tr>
<td><strong>EventBridge</strong></td>
<td>Event bus with rules</td>
<td>Routing events to different targets based on content</td>
</tr>
</tbody>
</table>
<p>The common pattern is SNS + SQS together: SNS fans out an event to multiple SQS queues, each consumed by a different microservice. This gives you both broadcast and buffering.</p>
<p>If you only need one consumer, skip SNS and use SQS directly. If you need content-based routing (e.g., "send order events to the billing service, send inventory events to the warehouse service"), EventBridge is the better choice.</p>
<h2 id="real-world-usage">Real-world usage</h2>
<p><strong>Netflix</strong> uses SNS to send push notifications about new content releases. When a new season drops, SNS fans out the notification to millions of subscriber endpoints simultaneously.</p>
<p><strong>Amazon</strong> itself uses SNS for order lifecycle notifications — placed, shipped, delivered — routing events to email, SMS, and mobile push depending on customer preferences.</p>
<p><strong>Walmart</strong> uses SNS for order fulfillment and in-store pickup notifications, integrating with their logistics systems to trigger real-time updates.</p>
<h2 id="where-sns-falls-short">Where SNS falls short</h2>
<ul>
<li><strong>No rich content.</strong> SNS messages are plain text (or JSON for application-to-application). If you need HTML email templates, open/click tracking, or A/B testing, you need SES or a third-party email service.</li>
<li><strong>No guaranteed ordering.</strong> Standard SNS topics don't guarantee message order. FIFO topics do, but they're limited to 300 messages/second per topic.</li>
<li><strong>Vendor lock-in.</strong> SNS integrates deeply with AWS services (Lambda, SQS, CloudWatch). Migrating to a different cloud later means rewriting all your pub/sub logic.</li>
<li><strong>SMS costs add up.</strong> International SMS delivery can be expensive ($0.02–0.15 per message depending on country), and you need to manage opt-in compliance yourself.</li>
</ul>
<h2 id="when-to-pick-something-else">When to pick something else</h2>
<ul>
<li><strong>Transactional email with templates:</strong> Use Amazon SES or SendGrid</li>
<li><strong>Marketing automation (drip campaigns, segmentation):</strong> Use Brevo, Mailchimp, or Customer.io</li>
<li><strong>Real-time chat or presence:</strong> Use WebSockets or a service like Ably/Pusher</li>
<li><strong>Cross-cloud pub/sub:</strong> Use Google Cloud Pub/Sub, Confluent Kafka, or NATS</li>
</ul>
<h2 id="getting-started">Getting started</h2>
<p>If you decide SNS fits, the setup is straightforward:</p>
<ol>
<li>Create an SNS topic in the AWS Console or via CloudFormation/CDK</li>
<li>Add subscribers (email, SQS queue, Lambda function, HTTP endpoint)</li>
<li>Publish messages via the AWS SDK from your application code</li>
<li>Configure a dead letter queue to catch failed deliveries</li>
<li>Set up CloudWatch alarms on <code>NumberOfNotificationsFailed</code></li>
</ol>
<p>The <a href="https://docs.aws.amazon.com/sns/latest/dg/welcome.html">AWS SNS documentation</a> covers each step with working examples in Python, Node.js, and Java.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/Amazon-SNS-for-Cost-Reduction-and-Message-Delivery-Assurance-in-Startups</link>
        <guid>https://blog.ratnesh-maurya.com/blog/Amazon-SNS-for-Cost-Reduction-and-Message-Delivery-Assurance-in-Startups</guid>
        <pubDate>Sun, 10 Dec 2023 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>AWS</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/Amazon-SNS-for-Cost-Reduction-and-Message-Delivery-Assurance-in-Startups.jpg" length="110429" type="image/jpeg" />
      </item>
      <item>
        <title><![CDATA[S3 Policies Explained: Bucket Policies vs IAM Policies vs ACLs]]></title>
        <description><![CDATA[How S3 access control works — bucket policies vs IAM policies vs ACLs, with JSON examples for public read, encryption enforcement, and user-scoped permissions.]]></description>
        <content:encoded><![CDATA[<p><img src="https://blog.ratnesh-maurya.com/images/blog/Understanding-S3-and-S3-Policies.jpg" alt="S3 Policies Explained: Bucket Policies vs IAM Policies vs ACLs" /></p><p>S3 has three overlapping access control systems — bucket policies, IAM policies, and ACLs — and the interaction between them confuses most people the first time. Here's how each one works, when to use which, and the JSON to copy for the most common scenarios.</p>
<h2 id="the-three-access-control-layers">The three access control layers</h2>
<p>Every S3 request is evaluated against all applicable policies. If any of them explicitly deny the request, it's denied. Otherwise, at least one policy must explicitly allow it.</p>
<table>
<thead>
<tr>
<th>Mechanism</th>
<th>Attached to</th>
<th>Written by</th>
<th>Best for</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Bucket policies</strong></td>
<td>The S3 bucket</td>
<td>Bucket owner</td>
<td>Cross-account access, public access, IP restrictions</td>
</tr>
<tr>
<td><strong>IAM policies</strong></td>
<td>IAM users/roles/groups</td>
<td>Account admin</td>
<td>Controlling what your own users and services can do</td>
</tr>
<tr>
<td><strong>ACLs</strong></td>
<td>Buckets or objects</td>
<td>Object owner</td>
<td>Legacy use only — AWS recommends disabling these</td>
</tr>
</tbody>
</table>
<p><strong>The rule of thumb:</strong> Use IAM policies for your own users, bucket policies for external access or bucket-wide rules, and ignore ACLs unless you're dealing with legacy configurations.</p>
<h2 id="anatomy-of-an-s3-policy">Anatomy of an S3 policy</h2>
<p>Every policy is a JSON document with these fields:</p>
<ul>
<li><strong>Version</strong> — always <code>"2012-10-17"</code> (the current policy language version)</li>
<li><strong>Statement</strong> — an array of permission rules, each containing:
<ul>
<li><strong>Effect</strong> — <code>"Allow"</code> or <code>"Deny"</code></li>
<li><strong>Principal</strong> — who the rule applies to (<code>"*"</code> for everyone, or a specific ARN)</li>
<li><strong>Action</strong> — which S3 operations (<code>s3:GetObject</code>, <code>s3:PutObject</code>, etc.)</li>
<li><strong>Resource</strong> — which bucket/objects (specified as an ARN)</li>
<li><strong>Condition</strong> (optional) — extra constraints like IP range, encryption type, or request origin</li>
</ul>
</li>
</ul>
<h2 id="common-policies-with-working-json">Common policies with working JSON</h2>
<h3 id="public-read-only-access">Public read-only access</h3>
<p>Makes all objects in a bucket publicly readable. Use this for static website hosting or public assets:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"Version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2012-10-17"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"Statement"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
    <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"Sid"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"PublicReadGetObject"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Allow"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Principal"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"*"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"s3:GetObject"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:s3:::my-bucket/*"</span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>This allows anyone to read objects but not list the bucket contents, upload, or delete. The <code>/*</code> in the Resource means all objects inside the bucket.</p>
<h3 id="deny-uploads-without-encryption">Deny uploads without encryption</h3>
<p>Forces all uploaded objects to use server-side encryption. Note: this uses <code>Deny</code> + a <code>StringNotEquals</code> condition, not <code>Allow</code>:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"Version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2012-10-17"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"Statement"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
    <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"Sid"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"DenyUnencryptedUploads"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Deny"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Principal"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"*"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"s3:PutObject"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:s3:::my-bucket/*"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Condition"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-attr">"StringNotEquals"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
          <span class="hljs-attr">"s3:x-amz-server-side-encryption"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"AES256"</span>
        <span class="hljs-punctuation">}</span>
      <span class="hljs-punctuation">}</span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>This is the correct pattern. A common mistake is using <code>Allow</code> with a <code>StringEquals</code> condition — that permits encrypted uploads but doesn't block unencrypted ones if another policy allows <code>s3:PutObject</code>.</p>
<h3 id="scoped-access-for-a-specific-iam-user">Scoped access for a specific IAM user</h3>
<p>Grants a single user read and write access to a bucket:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"Version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2012-10-17"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"Statement"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
    <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"Sid"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"UserReadWrite"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Allow"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Principal"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-attr">"AWS"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:iam::123456789012:user/deploy-bot"</span>
      <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"s3:GetObject"</span><span class="hljs-punctuation">,</span> <span class="hljs-string">"s3:PutObject"</span><span class="hljs-punctuation">,</span> <span class="hljs-string">"s3:DeleteObject"</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:s3:::my-bucket/*"</span>
    <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
    <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"Sid"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"UserListBucket"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Allow"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Principal"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-attr">"AWS"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:iam::123456789012:user/deploy-bot"</span>
      <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"s3:ListBucket"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:s3:::my-bucket"</span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>Note the two statements: object-level actions use <code>my-bucket/*</code> (objects inside), while <code>ListBucket</code> uses <code>my-bucket</code> (the bucket itself). Mixing these up is a common source of "Access Denied" errors.</p>
<h3 id="restrict-access-by-ip-range">Restrict access by IP range</h3>
<p>Allows access only from your office or VPN IP range:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"Version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2012-10-17"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"Statement"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
    <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"Sid"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"RestrictToOfficeIP"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Deny"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Principal"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"*"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"s3:*"</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
        <span class="hljs-string">"arn:aws:s3:::my-bucket"</span><span class="hljs-punctuation">,</span>
        <span class="hljs-string">"arn:aws:s3:::my-bucket/*"</span>
      <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"Condition"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-attr">"NotIpAddress"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
          <span class="hljs-attr">"aws:SourceIp"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"203.0.113.0/24"</span>
        <span class="hljs-punctuation">}</span>
      <span class="hljs-punctuation">}</span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<h2 id="bucket-policy-vs-iam-policy-when-to-use-which">Bucket policy vs IAM policy: when to use which</h2>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Use</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant another AWS account access to your bucket</td>
<td>Bucket policy (cross-account)</td>
</tr>
<tr>
<td>Make a bucket publicly readable</td>
<td>Bucket policy (Principal: <code>*</code>)</td>
</tr>
<tr>
<td>Control what your CI/CD pipeline can do</td>
<td>IAM policy on the pipeline's IAM role</td>
</tr>
<tr>
<td>Restrict access by IP or VPN</td>
<td>Bucket policy with Condition</td>
</tr>
<tr>
<td>Give a Lambda function access to a bucket</td>
<td>IAM policy on the Lambda execution role</td>
</tr>
<tr>
<td>Deny all public access organization-wide</td>
<td>S3 Block Public Access (account-level setting)</td>
</tr>
</tbody>
</table>
<p>In general: if the question is "who can access this bucket?", use a bucket policy. If the question is "what can this user/role do?", use an IAM policy.</p>
<h2 id="common-mistakes">Common mistakes</h2>
<ul>
<li><strong>Forgetting S3 Block Public Access.</strong> Even if your bucket policy allows public reads, the account-level Block Public Access setting overrides it. Check this first when public access isn't working.</li>
<li><strong>Resource ARN mismatch.</strong> <code>s3:ListBucket</code> needs the bucket ARN (<code>arn:aws:s3:::my-bucket</code>), while <code>s3:GetObject</code> needs the object ARN (<code>arn:aws:s3:::my-bucket/*</code>). This trips up almost everyone.</li>
<li><strong>Using ACLs.</strong> AWS recommends disabling ACLs on new buckets (S3 Object Ownership: "Bucket owner enforced"). Bucket policies and IAM policies cover every use case that ACLs used to handle, with better auditability.</li>
<li><strong>Overly broad wildcards.</strong> <code>"Action": "s3:*"</code> with <code>"Resource": "*"</code> is an admin-level policy. Scope both to the specific actions and bucket you need.</li>
</ul>
<p>The <a href="https://awspolicygen.s3.amazonaws.com/policygen.html">AWS Policy Generator</a> can help you build policies interactively if you're not sure about the syntax.</p>]]></content:encoded>
        <link>https://blog.ratnesh-maurya.com/blog/Understanding-S3-and-S3-Policies</link>
        <guid>https://blog.ratnesh-maurya.com/blog/Understanding-S3-and-S3-Policies</guid>
        <pubDate>Thu, 23 Nov 2023 00:00:00 GMT</pubDate>
        <author>ratneshmaurya2311@gmail.com (Ratnesh Maurya)</author>
        <category>AWS</category>
        <enclosure url="https://blog.ratnesh-maurya.com/images/blog/Understanding-S3-and-S3-Policies.jpg" length="89340" type="image/jpeg" />
      </item>
      </channel>
    </rss>