<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Building Kafka From Scratch]]></title><description><![CDATA[Building Kafka From Scratch]]></description><link>https://nagbhushanpai.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 21 Oct 2026 20:20:11 GMT</lastBuildDate><atom:link href="https://nagbhushanpai.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[GraphQL vs REST at Scale: The Tradeoffs Nobody Talks About]]></title><description><![CDATA[Choosing between GraphQL and REST isn't about which one is "better." It's about understanding where each architecture starts to break down as your product…]]></description><link>https://nagbhushanpai.hashnode.dev/graphql-vs-rest-at-scale-the-tradeoffs-nobody-talks-about</link><guid isPermaLink="true">https://nagbhushanpai.hashnode.dev/graphql-vs-rest-at-scale-the-tradeoffs-nobody-talks-about</guid><dc:creator><![CDATA[Nagbhushan_Pai]]></dc:creator><pubDate>Tue, 30 Jun 2026 17:17:22 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><em>Choosing between GraphQL and REST isn't about which one is "better." It's about understanding where each architecture starts to break down as your product, engineering team, and traffic grow.</em></p>
</blockquote>
<p>If you've spent enough time building APIs, you've probably seen the debate.</p>
<blockquote>
<p><strong>"GraphQL is the future."</strong></p>
</blockquote>
<blockquote>
<p><strong>"REST is simple and battle-tested."</strong></p>
</blockquote>
<p>Both statements are true.</p>
<p>And both are misleading.</p>
<p>After working on backend services that exposed GraphQL APIs while integrating with existing REST-based systems, I realized that the discussion isn't really about APIs.</p>
<p>It's about <strong>scale</strong>.</p>
<p>As your application grows, the challenges change—from returning JSON to managing hundreds of clients, thousands of requests, and multiple engineering teams.</p>
<p>Let's look at where each approach shines—and where it doesn't.</p>
<hr />
<h2>The Problem REST Was Designed to Solve</h2>
<p>REST models your API around <strong>resources</strong>.</p>
<pre><code class="language-http">GET /users/42
GET /users/42/orders
GET /products/15
</code></pre>
<p>Each endpoint represents a piece of your system.</p>
<p>It's simple.</p>
<p>Predictable.</p>
<p>Easy to cache.</p>
<p>And because every endpoint has a well-defined responsibility, debugging is usually straightforward.</p>
<p>For many applications...</p>
<p>REST is all you'll ever need.</p>
<hr />
<h2>Then the Frontend Gets More Complex</h2>
<p>Imagine building a dashboard.</p>
<p>It needs:</p>
<ul>
<li><p>User profile</p>
</li>
<li><p>Recent orders</p>
</li>
<li><p>Notifications</p>
</li>
<li><p>Saved addresses</p>
</li>
<li><p>Recommended products</p>
</li>
</ul>
<p>With REST, the frontend might call five different endpoints.</p>
<pre><code class="language-text">GET /user

GET /orders

GET /notifications

GET /addresses

GET /recommendations
</code></pre>
<p>Five requests.</p>
<p>Five database queries.</p>
<p>Five network round trips.</p>
<p>Nothing is technically wrong.</p>
<p>But mobile users on slower networks begin to notice.</p>
<hr />
<h2>Enter GraphQL</h2>
<p>GraphQL flips the relationship.</p>
<p>Instead of the server deciding what each endpoint returns...</p>
<p>The client asks for exactly what it needs.</p>
<pre><code class="language-graphql">query {
  user(id: 42) {
    name
    email
    orders {
      total
      status
    }
    notifications {
      title
    }
  }
}
</code></pre>
<p>One request.</p>
<p>One response.</p>
<p>No unnecessary data.</p>
<p>No missing fields.</p>
<p>This is why GraphQL became popular for applications with rich user interfaces.</p>
<hr />
<h2>The Hidden Cost of Flexibility</h2>
<p>GraphQL feels magical.</p>
<p>Until your API reaches production.</p>
<p>Because clients can request almost anything...</p>
<p>The server has to support almost everything.</p>
<p>Consider this query:</p>
<pre><code class="language-graphql">user {
  posts {
    comments {
      author {
        posts {
          comments {
            ...
          }
        }
      }
    }
  }
}
</code></pre>
<p>Looks harmless.</p>
<p>It isn't.</p>
<p>Without safeguards, one request can generate hundreds—or even thousands—of database queries.</p>
<p>This is known as the <strong>N+1 Query Problem</strong>.</p>
<p>It's one of the biggest performance pitfalls in GraphQL.</p>
<hr />
<h2>REST Has a Different Problem</h2>
<p>REST rarely suffers from deeply nested queries.</p>
<p>Instead...</p>
<p>It suffers from <strong>over-fetching</strong> and <strong>under-fetching</strong>.</p>
<p>Imagine requesting a user profile.</p>
<pre><code class="language-http">GET /users/42
</code></pre>
<p>The server returns:</p>
<pre><code class="language-json">{
  "id": 42,
  "name": "...",
  "email": "...",
  "createdAt": "...",
  "preferences": "...",
  "address": "...",
  "avatar": "...",
  "..."
}
</code></pre>
<p>The frontend only needed:</p>
<pre><code class="language-json">{
  "name": "...",
  "avatar": "..."
}
</code></pre>
<p>The extra data wastes bandwidth.</p>
<p>Now imagine the opposite.</p>
<p>The frontend also needs the user's recent orders.</p>
<p>Another request.</p>
<p>And another.</p>
<p>The application becomes <strong>chatty</strong>.</p>
<hr />
<h2>Caching Changes Everything</h2>
<p>One area where REST still dominates is caching.</p>
<p>Endpoints naturally map to URLs.</p>
<pre><code class="language-text">GET /products/123
</code></pre>
<p>CDNs and browsers understand this automatically.</p>
<p>GraphQL is different.</p>
<p>Most requests hit the same endpoint.</p>
<pre><code class="language-text">POST /graphql
</code></pre>
<p>Every query is unique.</p>
<p>Traditional HTTP caching becomes much harder.</p>
<p>Instead, GraphQL often relies on client-side caching libraries like Apollo or Relay.</p>
<p>Powerful?</p>
<p>Absolutely.</p>
<p>Simple?</p>
<p>Not quite.</p>
<hr />
<h2>What Happened During My Internship</h2>
<p>One of the projects I worked on involved building a <strong>GraphQL microservice</strong> using <strong>Django</strong>, <strong>Graphene</strong>, and <strong>PostgreSQL</strong>.</p>
<p>The goal wasn't to replace REST entirely.</p>
<p>It was to simplify how frontend applications accessed data.</p>
<p>Previously, different screens made multiple API calls to collect related information.</p>
<p>By exposing a GraphQL layer, the frontend could retrieve exactly the fields it needed in a single request, reducing unnecessary network traffic and simplifying client logic.</p>
<p>At the same time, our existing REST services continued to handle authentication, internal integrations, and other stable resource-oriented operations.</p>
<p>That experience reinforced an important lesson:</p>
<blockquote>
<p><strong>GraphQL and REST aren't competitors—they're tools that often work best together.</strong></p>
</blockquote>
<hr />
<h2>So... Which One Scales Better?</h2>
<p>It depends on what you're scaling.</p>
<table>
<thead>
<tr>
<th>Challenge</th>
<th>REST</th>
<th>GraphQL</th>
</tr>
</thead>
<tbody><tr>
<td>Simple CRUD APIs</td>
<td>✅ Excellent</td>
<td>Good</td>
</tr>
<tr>
<td>Mobile Applications</td>
<td>Good</td>
<td>✅ Excellent</td>
</tr>
<tr>
<td>Multiple Frontends</td>
<td>Good</td>
<td>✅ Excellent</td>
</tr>
<tr>
<td>CDN Caching</td>
<td>✅ Excellent</td>
<td>Limited</td>
</tr>
<tr>
<td>Large Engineering Teams</td>
<td>✅ Predictable</td>
<td>Requires strong schema governance</td>
</tr>
<tr>
<td>Preventing Over-fetching</td>
<td>Limited</td>
<td>✅ Built-in</td>
</tr>
<tr>
<td>Debugging</td>
<td>✅ Easier</td>
<td>More complex</td>
</tr>
<tr>
<td>API Evolution</td>
<td>Versioning</td>
<td>Schema evolution</td>
</tr>
</tbody></table>
<p>Neither architecture wins every category.</p>
<p>Each optimizes for different tradeoffs.</p>
<hr />
<h2>My Rule of Thumb</h2>
<p>After building and working with both styles, this is the heuristic I follow.</p>
<p>Use <strong>REST</strong> when:</p>
<ul>
<li><p>Resources are well-defined.</p>
</li>
<li><p>HTTP caching matters.</p>
</li>
<li><p>The API is relatively stable.</p>
</li>
<li><p>Simplicity is a priority.</p>
</li>
</ul>
<p>Use <strong>GraphQL</strong> when:</p>
<ul>
<li><p>Multiple clients consume the same backend.</p>
</li>
<li><p>Frontends need different views of the same data.</p>
</li>
<li><p>Over-fetching becomes a bottleneck.</p>
</li>
<li><p>Developer productivity outweighs operational complexity.</p>
</li>
</ul>
<p>And don't be afraid to combine them.</p>
<p>Many production systems expose REST APIs internally while providing a GraphQL gateway for frontend applications.</p>
<hr />
<h2>Final Thoughts</h2>
<p>The GraphQL vs REST debate often treats the two as opposing philosophies.</p>
<p>In reality, they're solving different problems.</p>
<p>REST optimizes for <strong>simplicity, predictability, and scalability at the infrastructure level</strong>.</p>
<p>GraphQL optimizes for <strong>developer experience, flexibility, and efficient data fetching</strong>.</p>
<p>As systems grow, neither architecture remains perfect.</p>
<p>The real engineering challenge isn't choosing one over the other.</p>
<p>It's understanding the tradeoffs—and knowing when each tool is the right one for the job.</p>
]]></content:encoded></item><item><title><![CDATA[How I Built an AI-Powered Deepfake Detector: Lessons from Training a Hybrid CNN + Vision Transformer]]></title><description><![CDATA[Deepfakes have become increasingly convincing, making it harder to distinguish manipulated videos from authentic ones. We wanted to understand whether…]]></description><link>https://nagbhushanpai.hashnode.dev/how-i-built-an-ai-powered-deepfake-detector-lessons-from-training-a-hybrid-cnn-vision-transformer</link><guid isPermaLink="true">https://nagbhushanpai.hashnode.dev/how-i-built-an-ai-powered-deepfake-detector-lessons-from-training-a-hybrid-cnn-vision-transformer</guid><dc:creator><![CDATA[Nagbhushan_Pai]]></dc:creator><pubDate>Tue, 30 Jun 2026 17:09:02 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><em>Deepfakes have become increasingly convincing, making it harder to distinguish manipulated videos from authentic ones. We wanted to understand whether modern computer vision techniques could reliably detect these forgeries, so we built our own end-to-end deepfake detection system—from training the model to deploying it as a web application.</em></p>
</blockquote>
<h2>Why Deepfake Detection?</h2>
<p>With the rise of generative AI, creating realistic fake videos has become easier than ever. While the technology has exciting creative applications, it also introduces serious risks, including misinformation, identity theft, and financial fraud.</p>
<p>Rather than relying on existing APIs, we wanted to understand the problem from first principles. Our goal was to build a complete system capable of taking a video as input, detecting manipulated facial features, and presenting the results through an intuitive web interface.</p>
<hr />
<h2>Defining the Pipeline</h2>
<p>Before writing any code, we broke the problem into smaller components.</p>
<pre><code class="language-plaintext">Video Upload
      │
      ▼
Frame Extraction
      │
      ▼
Face Detection (MTCNN)
      │
      ▼
Image Preprocessing
      │
      ▼
CNN + Vision Transformer
      │
      ▼
Frame Predictions
      │
      ▼
Final Video Classification
</code></pre>
<p>Each stage solved one specific problem, making the overall system easier to develop and debug.</p>
<hr />
<h2>Choosing the Right Dataset</h2>
<p>A model is only as good as the data it learns from.</p>
<p>We trained our detector using the <strong>FaceForensics++</strong> dataset, one of the most widely used benchmarks for deepfake research. It contains thousands of real and manipulated videos generated using multiple face manipulation techniques, allowing the model to learn a wide variety of visual artifacts instead of overfitting to a single editing method.</p>
<hr />
<h2>Why We Didn't Feed Entire Videos into the Model</h2>
<p>Processing an entire video would have required significantly more memory and computation.</p>
<p>Instead, we sampled a fixed number of representative frames from each video and extracted only the faces. Since facial regions contain the majority of deepfake artifacts, this approach dramatically reduced computation while preserving the most relevant information.</p>
<p>For face detection, we used <strong>MTCNN</strong>, which provided robust face localization even under varying lighting conditions and camera angles.</p>
<p>Each detected face was resized to <strong>224×224</strong> pixels before being passed to the classifier.</p>
<hr />
<h2>Building a Hybrid Model</h2>
<p>Instead of relying on a single architecture, we combined the strengths of Convolutional Neural Networks and Vision Transformers.</p>
<p>The CNN layers acted as local feature extractors, capturing subtle artifacts such as unnatural textures, blending inconsistencies, and compression noise.</p>
<p>These refined features were then passed into a Vision Transformer, which analyzed relationships across the entire face to identify inconsistencies that traditional CNNs might overlook.</p>
<p>This hybrid architecture allowed the model to learn both local and global visual patterns.</p>
<pre><code class="language-plaintext">Face Image
      │
      ▼
CNN Feature Extraction
      │
      ▼
Vision Transformer
      │
      ▼
Real / Fake Prediction
</code></pre>
<hr />
<h2>Training the Model</h2>
<p>Training involved much more than simply calling <code>model.fit()</code>.</p>
<p>We used:</p>
<ul>
<li><p>Adam optimizer with a learning rate of <strong>5 × 10⁻⁵</strong></p>
</li>
<li><p>Weighted Cross Entropy Loss to address class imbalance</p>
</li>
<li><p>Batch size of <strong>8</strong></p>
</li>
<li><p>80/20 train-test split</p>
</li>
<li><p>PyTorch as the deep learning framework</p>
</li>
</ul>
<p>The model was trained for multiple epochs while tracking both training and validation accuracy to ensure it generalized well instead of memorizing the dataset.</p>
<p>After training, the model achieved approximately <strong>92% accuracy</strong> on the test set.</p>
<p>While accuracy is important, we also evaluated confidence scores and frame-level predictions to better understand how the model behaved across different videos.</p>
<hr />
<h2>Turning the Model into a Web Application</h2>
<p>A trained model isn't useful if people can't interact with it.</p>
<p>We built a lightweight web application using <strong>Flask</strong> that allowed users to upload videos directly from their browser.</p>
<p>Once a video was uploaded, the backend performed the following steps:</p>
<ol>
<li><p>Extract video frames.</p>
</li>
<li><p>Detect faces using MTCNN.</p>
</li>
<li><p>Preprocess each face.</p>
</li>
<li><p>Run inference using the trained model.</p>
</li>
<li><p>Aggregate predictions across all processed frames.</p>
</li>
<li><p>Display the final prediction along with confidence scores.</p>
</li>
</ol>
<p>The frontend was intentionally simple, allowing users to drag and drop videos without requiring any technical knowledge.</p>
<hr />
<h2>Looking Beyond a Single Prediction</h2>
<p>Instead of classifying a video using just one frame, our system analyzed multiple frames and combined their predictions.</p>
<p>This made the detector significantly more robust.</p>
<p>For every uploaded video, the application displayed:</p>
<ul>
<li><p>Overall prediction (Real or Fake)</p>
</li>
<li><p>Confidence score</p>
</li>
<li><p>Number of analyzed frames</p>
</li>
<li><p>Frame-by-frame predictions</p>
</li>
<li><p>Detection history through an analytics dashboard</p>
</li>
</ul>
<p>This additional transparency helped users understand <em>why</em> a video received a particular classification instead of treating the model as a black box.</p>
<hr />
<h2>Challenges We Faced</h2>
<p>Building the system presented several practical challenges.</p>
<h3>Selecting Representative Frames</h3>
<p>Analyzing every frame was computationally expensive, while analyzing too few risked missing manipulated regions.</p>
<p>We settled on sampling a limited number of frames that provided a good balance between speed and accuracy.</p>
<h3>Face Detection Failures</h3>
<p>Videos with poor lighting, side profiles, or motion blur occasionally caused MTCNN to miss faces.</p>
<p>Handling these edge cases required additional validation and graceful error handling.</p>
<h3>Class Imbalance</h3>
<p>Real and fake samples were not perfectly balanced.</p>
<p>Using a weighted loss function prevented the model from becoming biased toward the majority class.</p>
<h3>Deployment Constraints</h3>
<p>Deep learning models can consume significant memory during inference.</p>
<p>To keep the application responsive, we limited the number of processed frames and optimized preprocessing wherever possible.</p>
<hr />
<h2>What We Learned</h2>
<p>This project taught us that building an AI application involves much more than training a neural network.</p>
<p>Data preparation, preprocessing, deployment, and user experience all have a direct impact on the quality of the final system.</p>
<p>Perhaps the biggest takeaway was that no single architecture is sufficient for every problem. Combining CNNs for local feature extraction with Vision Transformers for global context produced a stronger detector than relying on either approach alone.</p>
<p>More importantly, deploying the model as a complete web application transformed it from a research experiment into something users could actually interact with.</p>
<hr />
<h2>What's Next?</h2>
<p>Although the system performed well on FaceForensics++, there is still plenty of room for improvement.</p>
<p>Future iterations could include:</p>
<ul>
<li><p>Temporal modeling using video transformers instead of independent frame analysis.</p>
</li>
<li><p>Multi-modal detection by combining visual and audio signals.</p>
</li>
<li><p>Support for newer generative models and diffusion-based deepfakes.</p>
</li>
<li><p>GPU-accelerated inference for real-time processing.</p>
</li>
<li><p>Explainable AI techniques such as Grad-CAM to highlight manipulated facial regions.</p>
</li>
</ul>
<p>Deepfake generation techniques continue to evolve rapidly, and detectors must evolve alongside them. Building this project gave us a deeper appreciation for the challenges involved—not just in training accurate models, but in creating practical AI systems that people can use.</p>
]]></content:encoded></item><item><title><![CDATA[Building Kafka From Scratch: What Happens After You Call send()?]]></title><description><![CDATA[Kafka is one of those technologies everyone uses but very few truly understand. I wanted to change that—not by reading another whitepaper, but by building…]]></description><link>https://nagbhushanpai.hashnode.dev/building-kafka-from-scratch-what-happens-after-you-call-send</link><guid isPermaLink="true">https://nagbhushanpai.hashnode.dev/building-kafka-from-scratch-what-happens-after-you-call-send</guid><category><![CDATA[kafka]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Nagbhushan_Pai]]></dc:creator><pubDate>Tue, 30 Jun 2026 16:46:42 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><em>Kafka is one of those technologies everyone uses but very few truly understand. I wanted to change that—not by reading another whitepaper, but by building a simplified version from scratch.</em></p>
</blockquote>
<p>Most tutorials teach Kafka from the outside.</p>
<p>You create a producer.</p>
<p>You create a consumer.</p>
<p>You publish a message.</p>
<p>Everything magically works.</p>
<p>But...</p>
<ul>
<li><p>Where does the message actually go?</p>
</li>
<li><p>How does the consumer know what to read?</p>
</li>
<li><p>Why doesn't Kafka delete messages after they're consumed?</p>
</li>
<li><p>How can multiple consumers read the same data independently?</p>
</li>
</ul>
<p>Those questions led me to build my own miniature event streaming platform inspired by Kafka.</p>
<p>The goal wasn't to compete with Apache Kafka. It was to understand <em>why</em> Kafka is designed the way it is.</p>
<hr />
<h2>The First Attempt: Just Send Messages</h2>
<p>Every messaging system begins with one idea:</p>
<p>A producer sends data.</p>
<p>A consumer receives data.</p>
<pre><code class="language-plaintext">Producer
    │
    ▼
 Queue
    │
    ▼
Consumer
</code></pre>
<p>A simple queue works surprisingly well.</p>
<p>Until you ask two questions.</p>
<p><strong>What happens if the consumer crashes?</strong></p>
<p>The message disappears.</p>
<p><strong>What if another consumer wants to read the same message?</strong></p>
<p>Too late. It has already been removed.</p>
<p>Queues are great for task distribution.</p>
<p>Kafka solves a different problem.</p>
<hr />
<h2>Instead of a Queue, Think Like a Journal</h2>
<p>Rather than deleting messages, Kafka keeps appending them to a log.</p>
<p>Imagine writing in a notebook.</p>
<pre><code class="language-plaintext">0  User Registered
1  Payment Successful
2  Email Sent
3  Order Delivered
</code></pre>
<p>Nothing gets erased.</p>
<p>New events are simply written at the end.</p>
<p>This tiny design decision changes everything.</p>
<p>Appending is incredibly fast because disks are optimized for sequential writes.</p>
<p>More importantly, history is preserved.</p>
<p>That became the foundation of my implementation.</p>
<p>Instead of removing messages after consumption, I stored every event in append-only log files.</p>
<hr />
<h2>Reading Without Deleting</h2>
<p>Now another question appears.</p>
<p>If messages aren't deleted...</p>
<p><strong>How does a consumer know what it has already processed?</strong></p>
<p>The answer is surprisingly elegant.</p>
<p>Every message gets an <strong>offset</strong>.</p>
<pre><code class="language-plaintext">Offset    Event

0         Login
1         Upload
2         Payment
3         Logout
</code></pre>
<p>The consumer simply remembers the last offset it processed.</p>
<pre><code class="language-plaintext">Consumer A

Current Offset = 2
</code></pre>
<p>The next read starts from offset <strong>3</strong>.</p>
<p>Nothing is deleted.</p>
<p>Nothing is copied.</p>
<p>Just a number.</p>
<p>Implementing offset tracking was one of my favorite parts of the project because it immediately enabled message replay and recovery after failures.</p>
<hr />
<h2>One Log Isn't Enough</h2>
<p>A single log works...</p>
<p>...until thousands of producers start writing simultaneously.</p>
<p>Now everyone is waiting for the same file.</p>
<p>The obvious solution?</p>
<p>Split the log.</p>
<pre><code class="language-plaintext">Orders

├── Partition 0
├── Partition 1
└── Partition 2
</code></pre>
<p>Messages are distributed across partitions using a hashing strategy.</p>
<pre><code class="language-plaintext">partition = hash(key) % number_of_partitions
</code></pre>
<p>Suddenly, multiple producers can write in parallel.</p>
<p>This dramatically improves throughput while keeping ordering guarantees inside each partition.</p>
<p>I implemented topic partitioning using this exact idea.</p>
<hr />
<h2>Building the Broker</h2>
<p>Once producers and consumers existed, I needed something in the middle.</p>
<p>The broker became the brain of the system.</p>
<p>Its responsibilities were simple:</p>
<ul>
<li><p>Accept incoming messages</p>
</li>
<li><p>Choose the correct partition</p>
</li>
<li><p>Persist data to disk</p>
</li>
<li><p>Serve consumer requests</p>
</li>
<li><p>Track offsets</p>
</li>
</ul>
<p>Instead of producers writing directly to files, everything flowed through the broker.</p>
<pre><code class="language-plaintext">Producer

      │

      ▼

   Broker

 ┌──────────┐
 │ Partition│
 │ Selection│
 └──────────┘

      │

      ▼

 Append Log
</code></pre>
<p>This separation made the architecture much cleaner and much closer to how real event streaming systems operate.</p>
<hr />
<h2>Consumers Needed Memory Too</h2>
<p>At first, consumers simply kept asking:</p>
<blockquote>
<p>"Do you have anything new?"</p>
</blockquote>
<p>The broker responded with every message after the consumer's last offset.</p>
<p>This worked.</p>
<p>But what if several consumers belonged to the same application?</p>
<p>If everyone read every partition...</p>
<p>...work would be duplicated.</p>
<p>So I added consumer groups.</p>
<p>Each consumer receives ownership of specific partitions.</p>
<pre><code class="language-plaintext">Consumer Group

Consumer A  → Partition 0

Consumer B  → Partition 1

Consumer C  → Partition 2
</code></pre>
<p>Now processing scales horizontally without duplicate work.</p>
<hr />
<h2>Keeping Consumers Alive</h2>
<p>A new challenge appeared.</p>
<p>How does the broker know whether a consumer is still alive?</p>
<p>I solved this with heartbeats.</p>
<p>Each consumer periodically sends a small heartbeat message.</p>
<p>If the broker stops receiving heartbeats, it assumes the consumer has failed.</p>
<p>The abandoned partitions are automatically reassigned to another consumer in the same group.</p>
<p>This was one of the most interesting parts of the project because it introduced fault tolerance without making the system overly complex.</p>
<hr />
<h2>Persistence Changed Everything</h2>
<p>Keeping everything in memory makes development easy.</p>
<p>It also means losing everything after a restart.</p>
<p>To avoid that, every message was written to disk using append-only logs.</p>
<p>When the broker restarted, it simply rebuilt its state from the stored log files.</p>
<p>No database.</p>
<p>No recovery scripts.</p>
<p>Just replaying the history.</p>
<p>This reinforced one of Kafka's most elegant ideas:</p>
<blockquote>
<p>The log is the source of truth.</p>
</blockquote>
<hr />
<h2>Measuring Performance</h2>
<p>Once the system worked, I wanted to understand how it behaved under load.</p>
<p>I benchmarked the broker using different partition counts.</p>
<ul>
<li><p>4 partitions</p>
</li>
<li><p>8 partitions</p>
</li>
<li><p>16 partitions</p>
</li>
</ul>
<p>The throughput remained fairly consistent at around <strong>330–338 messages per second</strong>.</p>
<p>The biggest bottlenecks weren't the algorithms themselves.</p>
<p>They were:</p>
<ul>
<li><p>Synchronous disk writes</p>
</li>
<li><p>JSON serialization overhead</p>
</li>
<li><p>Python thread scheduling</p>
</li>
<li><p>Network I/O between clients and the broker</p>
</li>
</ul>
<p>Even though the implementation was intentionally simple, the benchmarks showed how partitioning improves concurrency while other parts of the system become the limiting factors.</p>
<hr />
<h2>What I Didn't Build</h2>
<p>Apache Kafka is the result of years of engineering.</p>
<p>My implementation intentionally focused on understanding the core concepts rather than reproducing every production feature.</p>
<p>It doesn't include:</p>
<ul>
<li><p>Multi-broker replication</p>
</li>
<li><p>Leader election</p>
</li>
<li><p>Log compaction</p>
</li>
<li><p>Zero-copy networking</p>
</li>
<li><p>Compression</p>
</li>
<li><p>Distributed consensus</p>
</li>
</ul>
<p>And that's okay.</p>
<p>The objective wasn't feature parity.</p>
<p>It was understanding the architecture.</p>
<hr />
<h2>Final Thoughts</h2>
<p>Building a Kafka-inspired system completely changed the way I think about event streaming.</p>
<p>The biggest lesson wasn't about producers or consumers.</p>
<p>It was about logs.</p>
<p>Once you realize that Kafka is fundamentally a distributed, append-only log with smart indexing and offset management, many of its design decisions suddenly make perfect sense.</p>
<p>Sometimes the best way to understand a complex system isn't to read another tutorial.</p>
<p>It's to build one yourself.</p>
]]></content:encoded></item></channel></rss>