<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://www.firasesbai.com/feed.xml" rel="self" type="application/atom+xml"/><link href="https://www.firasesbai.com/" rel="alternate" type="text/html"/><updated>2026-05-07T07:44:22+00:00</updated><id>https://www.firasesbai.com/feed.xml</id><title type="html">Firas Esbai</title><subtitle>The Blog and Portfolio of Firas Esbai. </subtitle><author><name>Firas Esbai</name></author><entry><title type="html">How AI is Changing the Data Engineering Role</title><link href="https://www.firasesbai.com/articles/2026/03/18/how-ai-is-changing-data-engineering-role.html" rel="alternate" type="text/html" title="How AI is Changing the Data Engineering Role"/><published>2026-03-18T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2026/03/18/how-ai-is-changing-data-engineering-role</id><content type="html" xml:base="https://www.firasesbai.com/articles/2026/03/18/how-ai-is-changing-data-engineering-role.html"><![CDATA[<p><em>In this article, I will highlight some of the changes that I’m seeing in the field and how they are impacting the role of the data engineer.</em></p> <p>The recent developments in the AI space have impacted the way people live and work. This is particularly true for software and data engineers. Statements like <em>“Coding is largely solved”</em> and predictions that most code will be AI generated in some near future (timeline is different depending on whom you’re asking) have definitely contributed, among other things, to a challenging job market especially for entry-level and junior profiles.</p> <p>In such an environment, and as the coding agents and models keep getting better, it is time to embrace the fact that change is inevitable and identify how exactly AI is affecting our jobs and what can we do about it.</p> <p><em>So let’s get started!</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#why-business-context-matters-more-for-data-engineers" id="markdown-toc-why-business-context-matters-more-for-data-engineers">Why Business Context Matters More for Data Engineers</a></li> <li><a href="#why-data-quality-still-matters-in-the-age-of-ai" id="markdown-toc-why-data-quality-still-matters-in-the-age-of-ai">Why Data Quality Still Matters in the Age of AI</a></li> <li><a href="#same-data-engineering-principles-new-architectures" id="markdown-toc-same-data-engineering-principles-new-architectures">Same Data Engineering Principles, New Architectures</a></li> <li><a href="#recap" id="markdown-toc-recap">Recap</a></li> </ul> <hr/> <h2 id="why-business-context-matters-more-for-data-engineers">Why Business Context Matters More for Data Engineers</h2> <p>Early in your career, it is natural to lean towards focusing on the technical skills in order to build a good foundation. However, this approach will only get you so far. Data engineers operate at the intersection of business strategy and technology. The role of the data engineer is not just building and maintaining pipelines. While that is still important, understanding the business context and being able to translate the requirements is what makes you a better data engineer.</p> <p>Business stakeholders will ask you for example to provide a real-time dashboard. But what does it really mean real-time here? Maybe they meant the data needs to be up to date when they look at it every morning? Maybe they need the data to be accurate when they generate a report from it once a week? Jumping into solutioning mode without clearly understanding the requirements will result in an over-engineered and expensive solution. In the age of AI, this becomes even more important.</p> <p>Data engineering is definitely not going away. It might be called differently at some point but a data pipeline is still a data pipeline. You just have to understand what it is feeding.</p> <p>For example, training a large language model (LLM) requires a lot of data. This data is usually unstructured and spread across different sources and APIs. Processing the data, storing it and making it available across the different stages of the model training still requires data engineering expertise. As not every company will be training its own language model, we can take another emerging pattern as an example: <em>RAG</em>.</p> <p>RAG stands for Retrieval-Augmented Generation. It is a cost-effective approach to overcome the limitation of outdated data and improve the output of the model or agent by feeding the correct internal data. Who knows better than the data engineering team which data to ingest? How to transform it and how to best store it for faster retrieval? This is all still data engineering work and has nothing specific to AI.</p> <p>While I can keep iterating on examples, you might argue though that since coding agents are getting much better nowadays, they are able to generate code for data transformation workflows, write SQL queries and create dashboards. However, this does not necessarily imply removing the human from the loop. How a pipeline is built depends on a good understanding of the business and its unique needs. Without this context, these agents will just end up hallucinating and making things up.</p> <p>You should treat them as tools that support and amplify your work by cutting down time on grunt work and boilerplate code. This will allow you to focus more on high-level design and optimizations.</p> <p>As the creator of Claude Code said:</p> <figure> <picture> <source srcset="/assets/images/articles/31_creator_claude_code_tweet.webp" type="image/webp"/> <img src="/assets/images/articles/31_creator_claude_code_tweet.png" alt="Tweet from the creator of Claude Code" loading="lazy"/> </picture> <figcaption>Figure 1: Tweet from the creator of Claude Code- <a href="https://x.com/bcherny/status/2022762422302576970">Source</a></figcaption> </figure> <p>Therefore, understanding how these coding agents work and how to integrate them in your workflow is necessary. For a practical guide on AI coding agents, check out <a href="https://www.firasesbai.com/articles/2026/02/04/ai-coding-agents.html">this article</a>. You can also read about my own experience with these tools in <a href="https://www.firasesbai.com/articles/2025/09/25/vibe-coding-learnings.html">Learnings from Vibe Coding</a>.</p> <h2 id="why-data-quality-still-matters-in-the-age-of-ai">Why Data Quality Still Matters in the Age of AI</h2> <p>As the volume of the data that we need to process is only going to increase, so are the stakes in your data quality. Flawed and poor quality input data inevitably produces similar output quality. This is what is known as: <em>Garbage in, Garbage Out.</em></p> <p>This is not a new problem. Data quality has been one of the biggest pain points for data teams fighting erroneous, missing or otherwise inaccurate data. The impacts of bad data go beyond drawing bad insights and sometimes even wrong conclusions. It can also result in wasted resources and money. If teams have to correct these failures all the time through backfilling pipelines, depending on the scale of data, this could take up many hours both in terms of compute and engineering capacity. In addition, it will affect product adoption and damage the overall organization’s reputation.</p> <p>Now building AI systems also requires high-quality trustworthy data. If an organization is still struggling with data quality issues, any AI initiative will probably fail. AI adoption is accelerating and most companies are investing in it but without prioritizing the foundational building blocks, like having good data quality for starters, it will only lead to project failures. This is why I think the data engineer’s role will become even more important going forward. Supporting AI initiatives requires improving and maintaining good data quality and designing systems that meet the new scale, which we will discuss in the next section.</p> <h2 id="same-data-engineering-principles-new-architectures">Same Data Engineering Principles, New Architectures</h2> <p>Thinking in abstractions has been helpful in navigating this crazy landscape. An example in the data field would be the <a href="https://www.firasesbai.com/articles/2023/09/10/understanding-modern-data-stack.html">Modern Data Stack</a>. While a stack is just a combination of technologies and components, they do provide structure for building solutions. A recent similar emerging idea is the <em>AI Stack</em>. The purpose of this article is not to dive into the latter, but rather use it as a reference to highlight a key component around data engineering. There are multiple definitions out there for this new stack and its components but in each one of them we have a data layer. Given the specific requirements needed for building AI applications and the sheer amount of data, data engineers are faced with new challenges.</p> <p>Continuing with the RAG use case mentioned earlier and in order to provide relevant context to the model, external data need to be fed in the form of numerical vectors called embeddings.</p> <p>To achieve that, the high-level process involves splitting the data into smaller chunks, transforming them into embeddings and storing them in a specialized database called vector database. For a hands-on example of this process, check out how I <a href="https://www.firasesbai.com/articles/2026/01/24/building-semantic-search.html">built semantic search for this blog</a>. These are optimized for storing, indexing and searching high-dimensional vector embeddings. While it may seem as a new type of architecture, underneath it is mainly about setting up pipelines, ingesting the data, managing the data flow, optimizing the embeddings and retrieval; all the things you would expect a typical data engineering team to be doing. As the volume of vector data grows, maintaining low-latency search and efficient indexing becomes challenging. In addition, ensuring fine-grained access control, encryption at rest and in transit, and audit trails are essential components of governance that need to be put in place.</p> <h2 id="recap">Recap</h2> <p>The data engineering role is integral to the business and holds a central role in bringing AI initiatives to life. Having a strong data foundation is not optional for any AI initiative’s success and that puts the spotlight on data engineers who are no longer working behind the scenes. It goes without saying that while data engineering teams have seen AI-led productivity improvements, complexity has also increased. Data engineers need to embrace the change as the role will evolve significantly and the day-to-day activities will change.</p> <p>With this we have reached the end of this post, I hope you enjoyed it!</p> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <p><em>Happy learning!</em></p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="Data Engineering"/><summary type="html"><![CDATA[How AI is reshaping data engineering, from business context and data pipelines to data quality, RAG, vector databases and new architectures]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/articles/31_creator_claude_code_tweet.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/articles/31_creator_claude_code_tweet.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">AI Coding Agents: A Practical Guide</title><link href="https://www.firasesbai.com/articles/2026/02/04/ai-coding-agents.html" rel="alternate" type="text/html" title="AI Coding Agents: A Practical Guide"/><published>2026-02-04T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2026/02/04/ai-coding-agents</id><content type="html" xml:base="https://www.firasesbai.com/articles/2026/02/04/ai-coding-agents.html"><![CDATA[<p><em>In this article, I’ll break down what makes an AI agent, explain why I’ve settled on Cline as my primary tool, and explore the techniques that extend an agent’s capabilities.</em></p> <p>AI coding tools have moved beyond simple suggestions. Today’s agents can read your codebase, execute commands, search the web, and complete multi-step tasks. As things are changing rapidly with new features and terminology appearing every week that each promises to revolutionize how we write code, I wanted to understand the bigger picture: What are the core building blocks that all these tools share? How do features interconnect? And most importantly, how can I use this knowledge to work more effectively?</p> <p>This article maps out the anatomy of an AI coding agent, using Cline as the primary example while drawing connections to other tools in the ecosystem.</p> <p><em>So let’s get started!</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#foundation-what-is-an-agent" id="markdown-toc-foundation-what-is-an-agent">Foundation: What is an Agent?</a></li> <li><a href="#why-cline" id="markdown-toc-why-cline">Why Cline?</a></li> <li><a href="#context-layer-what-the-agent-knows" id="markdown-toc-context-layer-what-the-agent-knows">Context Layer: What the Agent Knows</a> <ul> <li><a href="#context-engineering" id="markdown-toc-context-engineering">Context Engineering</a></li> <li><a href="#memory-bank" id="markdown-toc-memory-bank">Memory Bank</a></li> </ul> </li> <li><a href="#instructions-layer-how-the-agent-behaves" id="markdown-toc-instructions-layer-how-the-agent-behaves">Instructions Layer: How the Agent Behaves</a> <ul> <li><a href="#custom-instructions" id="markdown-toc-custom-instructions">Custom Instructions</a></li> <li><a href="#skills" id="markdown-toc-skills">Skills</a></li> </ul> </li> <li><a href="#tools-layer-what-the-agent-can-do" id="markdown-toc-tools-layer-what-the-agent-can-do">Tools Layer: What the Agent Can Do</a> <ul> <li><a href="#tools" id="markdown-toc-tools">Tools</a></li> <li><a href="#mcp" id="markdown-toc-mcp">MCP</a></li> </ul> </li> <li><a href="#recap" id="markdown-toc-recap">Recap</a></li> <li><a href="#resources" id="markdown-toc-resources">Resources</a></li> </ul> <hr/> <h2 id="foundation-what-is-an-agent">Foundation: What is an Agent?</h2> <p>GitHub copilot was initially released in June 2021. A collaboration between GitHub and OpenAI resulted in an AI powered tool with the premise to help developers code faster leveraging OpenAI’s Codex model. The tool mainly offered what is now sometimes referred to as “autocomplete on steroids”. However, things evolved rapidly and by 2024 the AI coding tools got more advanced especially with integrations into IDEs to reach what we are currently witnessing as the emergence of AI coding agents. But what is an AI agent?</p> <p>In order to understand what is an AI Agent and identify its foundational building blocks, we will first look at different definitions from AI research labs and technology companies.</p> <p>According to <a href="https://developers.openai.com/tracks/building-agents/">OpenAI</a>, an agent is <em>An AI system that has instructions (what it should do), guardrails (what it should not do), and access to tools (what it can do) to take action on the user’s behalf</em>.</p> <p>Google has a similar <a href="https://cloud.google.com/discover/what-are-ai-agents?hl=en">definition</a> with an emphasis on the capabilities of the model:</p> <p><em>AI agents are software systems that use AI to pursue goals and complete tasks on behalf of users. They show reasoning, planning, and memory and have a level of autonomy to make decisions, learn, and adapt.</em></p> <p>Looking to <a href="https://www.anthropic.com/engineering/building-effective-agents">Anthropic’s definition</a>, they have a more broad definition of an <strong>agentic system</strong> encompassing both:</p> <ul> <li><em><strong>Workflows</strong> that are systems where LLMs and tools are orchestrated through predefined code paths.</em></li> <li><em><strong>Agents</strong>, on the other hand, are systems where LLMs dynamically direct their own processes and tool usage, maintaining control over how they accomplish tasks.</em></li> </ul> <p>To simplify things and have a set of building blocks that we can use later as foundations to build upon and extend, we can consider an agent to be composed of the following:</p> <div class="jekyll-diagrams diagrams mermaid"> <svg id="my-svg" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 887.375px; background-color: white;" viewBox="0 0 887.375 140" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333}@keyframes edge-animation-frame{from{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round}#my-svg .error-icon{fill:#522}#my-svg .error-text{fill:#522;stroke:#522}#my-svg .edge-thickness-normal{stroke-width:1px}#my-svg .edge-thickness-thick{stroke-width:3.5px}#my-svg .edge-pattern-solid{stroke-dasharray:0}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none}#my-svg .edge-pattern-dashed{stroke-dasharray:3}#my-svg .edge-pattern-dotted{stroke-dasharray:2}#my-svg .marker{fill:#333;stroke:#333}#my-svg .marker.cross{stroke:#333}#my-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px}#my-svg p{margin:0}#my-svg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333}#my-svg .cluster-label text{fill:#333}#my-svg .cluster-label span{color:#333}#my-svg .cluster-label span p{background-color:transparent}#my-svg .label text,#my-svg span{fill:#333;color:#333}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#ececff;stroke:#9370db;stroke-width:1px}#my-svg .rough-node .label text,#my-svg .node .label text,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-anchor:middle}#my-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px}#my-svg .rough-node .label,#my-svg .node .label,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-align:center}#my-svg .node.clickable{cursor:pointer}#my-svg .root .anchor path{fill:#333!important;stroke-width:0;stroke:#333}#my-svg .arrowheadPath{fill:#333}#my-svg .edgePath .path{stroke:#333;stroke-width:2px}#my-svg .flowchart-link{stroke:#333;fill:none}#my-svg .edgeLabel{background-color:rgba(232,232,232,0.8);text-align:center}#my-svg .edgeLabel p{background-color:rgba(232,232,232,0.8)}#my-svg .edgeLabel rect{opacity:.5;background-color:rgba(232,232,232,0.8);fill:rgba(232,232,232,0.8)}#my-svg .labelBkg{background-color:rgba(232,232,232,0.5)}#my-svg .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#my-svg .cluster text{fill:#333}#my-svg .cluster span{color:#333}#my-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80,100%,96.2745098039%);border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#my-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333}#my-svg rect.text{fill:none;stroke-width:0}#my-svg .icon-shape,#my-svg .image-shape{background-color:rgba(232,232,232,0.8);text-align:center}#my-svg .icon-shape p,#my-svg .image-shape p{background-color:rgba(232,232,232,0.8);padding:2px}#my-svg .icon-shape rect,#my-svg .image-shape rect{opacity:.5;background-color:rgba(232,232,232,0.8);fill:rgba(232,232,232,0.8)}#my-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em}#my-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif}</style><g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"/><g class="edgePaths"><path d="M103.594,70L107.76,70C111.927,70,120.26,70,127.927,70C135.594,70,142.594,70,146.094,70L149.594,70" id="L_Input_Core_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_Input_Core_0" data-points="W3sieCI6MTAzLjU5Mzc1LCJ5Ijo3MH0seyJ4IjoxMjguNTkzNzUsInkiOjcwfSx7IngiOjE1My41OTM3NSwieSI6NzB9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M721.344,70L725.51,70C729.677,70,738.01,70,745.677,70C753.344,70,760.344,70,763.844,70L767.344,70" id="L_Core_Output_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_Core_Output_0" data-points="W3sieCI6NzIxLjM0Mzc1LCJ5Ijo3MH0seyJ4Ijo3NDYuMzQzNzUsInkiOjcwfSx7IngiOjc3MS4zNDM3NSwieSI6NzB9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" data-id="L_Input_Core_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_Core_Output_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="root" transform="translate(145.59375, 0)"><g class="clusters"><g class="cluster" id="Core" data-look="classic"><rect style="" x="8" y="8" width="567.75" height="124"/><g class="cluster-label" transform="translate(261.625, 8)"><foreignObject width="60.5" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>AI Agent</p></span></div></foreignObject></g></g></g><g class="edgePaths"><path d="M187.313,70L193.563,70C199.813,70,212.313,70,224.146,70C235.979,70,247.146,70,252.729,70L258.313,70" id="L_I_M_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_I_M_0" data-points="W3sieCI6MTg3LjMxMjUsInkiOjcwfSx7IngiOjIyNC44MTI1LCJ5Ijo3MH0seyJ4IjoyNjIuMzEyNSwieSI6NzB9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M365.891,57.82L372.141,56.35C378.391,54.88,390.891,51.94,402.743,51.835C414.595,51.73,425.8,54.461,431.402,55.826L437.004,57.191" id="L_M_T_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_M_T_0" data-points="W3sieCI6MzY1Ljg5MDYyNSwieSI6NTcuODE5NjY5MjYyNDAyNjZ9LHsieCI6NDAzLjM5MDYyNSwieSI6NDl9LHsieCI6NDQwLjg5MDYyNSwieSI6NTguMTM3ODg0MTQ0NjgzMTd9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M440.891,81.862L434.641,83.385C428.391,84.908,415.891,87.954,404.04,88.16C392.189,88.365,380.986,85.731,375.385,84.413L369.784,83.096" id="L_T_M_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_T_M_0" data-points="W3sieCI6NDQwLjg5MDYyNSwieSI6ODEuODYyMTE1ODU1MzE2ODN9LHsieCI6NDAzLjM5MDYyNSwieSI6OTF9LHsieCI6MzY1Ljg5MDYyNSwieSI6ODIuMTgwMzMwNzM3NTk3MzR9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" data-id="L_I_M_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_M_T_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_T_M_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" id="flowchart-I-1" transform="translate(116.40625, 70)"><rect class="basic label-container" style="" x="-70.90625" y="-27" width="141.8125" height="54"/><g class="label" style="" transform="translate(-40.90625, -12)"><rect/><foreignObject width="81.8125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Instructions</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-M-2" transform="translate(314.1015625, 70)"><rect class="basic label-container" style="" x="-51.7890625" y="-27" width="103.578125" height="54"/><g class="label" style="" transform="translate(-21.7890625, -12)"><rect/><foreignObject width="43.578125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Model</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-T-4" transform="translate(489.5703125, 70)"><rect class="basic label-container" style="" x="-48.6796875" y="-27" width="97.359375" height="54"/><g class="label" style="" transform="translate(-18.6796875, -12)"><rect/><foreignObject width="37.359375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Tools</p></span></div></foreignObject></g></g></g></g><g class="node default" id="flowchart-Input-0" transform="translate(55.796875, 70)"><rect class="basic label-container" style="" x="-47.796875" y="-27" width="95.59375" height="54"/><g class="label" style="" transform="translate(-17.796875, -12)"><rect/><foreignObject width="35.59375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Input</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-Output-7" transform="translate(825.359375, 70)"><rect class="basic label-container" style="" x="-54.015625" y="-27" width="108.03125" height="54"/><g class="label" style="" transform="translate(-24.015625, -12)"><rect/><foreignObject width="48.03125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Output</p></span></div></foreignObject></g></g></g></g></g></svg> </div> <p style="text-align:center;">Figure 1: AI Agent Building Blocks</p> <p>It is worth highlighting that the agent operates in a continuous loop: it observes the current state, reasons about what to do next, takes actions through tools and observes the results. This cycle continues until the task is completed or the user intervenes.</p> <p>With this understanding, let’s see why I consider <strong>Cline</strong> as my go-to AI coding assistant and explore later best practices and features that extend its functionality.</p> <h2 id="why-cline">Why Cline?</h2> <p>The landscape of AI coding tools is evolving rapidly. On one hand, the underlying models powering these tools keep getting better with a new model at the top of the leaderboard every other month. On the other hand, integrated systems such as Claude Code limit your flexibility to switch models or explore other features that might not be available there.</p> <p>Constantly switching and chasing the new kid on the block can be taxing. Of course this does not mean you shouldn’t try them out, but you might end up using multiple tools and managing multiple subscriptions. In an attempt to reduce this overhead, I have found <strong><a href="https://github.com/cline/cline">Cline</a></strong> <strong><a href="https://cline.bot/">- a free VSCode plugin</a></strong> to be uniquely valuable.</p> <p>What stood out to me is Cline’s philosophy; You get access to any model, you don’t have to choose a particular tool and you get the latest proven features from the industry without suffering the tool fatigue.</p> <p>In the following sections, we will look into techniques and concepts that extend and improve the functionality of an agent and how they can be leveraged in Cline.</p> <h2 id="context-layer-what-the-agent-knows">Context Layer: What the Agent Knows</h2> <h3 id="context-engineering">Context Engineering</h3> <p>Context is a collection of information an AI agent uses to understand the user’s request. This is however a finite and limited resource because every AI model has a <strong>context window</strong> which is the maximum amount it can process, measured in tokens, in a single conversation.</p> <p>What started as crafting optimized effective prompts, known as prompt engineering, has evolved to finding the best strategy for managing the entire context and choosing what to include in the limited context window.</p> <figure> <picture> <source srcset="/assets/images/articles/30_prompt_engineering_vs_context_engineering.webp" type="image/webp"/> <img src="/assets/images/articles/30_prompt_engineering_vs_context_engineering.png" alt="prompt engineering vs context engineering" loading="lazy"/> </picture> <figcaption>Figure 2: Prompt engineering vs Context engineering - <a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents">Image Source</a></figcaption> </figure> <p>Besides the model’s system prompt, when you start a new task or conversation, the context window will include the user’s query along with needed context, requirements, documentation and active files. As you interact more with model, the conversation history is also included. In addition, tools like Cline will include additional dynamic context needed to achieve the task at hand. For instance, Cline automatically discovers:</p> <ul> <li>project structure and file organization</li> <li>Import relationships and dependencies</li> <li>Code patterns and conventions</li> <li>Configuration files and settings</li> <li>Recent changes and git history (when using @git)</li> </ul> <p>On complex projects and as the current conversation approaches the context window limit, a standard solution would be using summarization technique to compress context preserving important information and allows you to keep working. However, this is not ideal as you could still sacrifice important context.</p> <p>Though things like documentation of your project, architectural decisions and design patterns are not persisted across different sessions but still required to get more accurate and relevant responses. This is where the memory bank comes in.</p> <h3 id="memory-bank">Memory Bank</h3> <p>Depending on the tools you use, files like <strong>CLAUDE.md</strong> or <strong>GEMINI.md</strong>, are used to provide persistent guidelines and preferences across sessions. Automatically loaded into the context, they act as a long term memory for the agent. An attempt to standardize this across many agents, resulted in this simple open format: <a href="https://agents.md/#examples">AGENTS.md</a></p> <p>Cline’s memory bank on the other hand, is not a specific feature but rather a methodology that takes this into a structured documentation that allows to maintain knowledge across sessions. It is simply a group of markdown files stored in your repository that goes beyond a single README.md file.</p> <h2 id="instructions-layer-how-the-agent-behaves">Instructions Layer: How the Agent Behaves</h2> <h3 id="custom-instructions">Custom Instructions</h3> <p>Setting custom instructions help the AI generate code that is more aligned with your specific conventions, frameworks and use cases. These project specific customizations lead to:</p> <ul> <li>Consistency across team members when receiving suggestions that adhere to the same coding standards and practices.</li> <li>More accurate and relevant suggestions knowing your tech stack, frameworks and tools.</li> <li>Reduce the time spent on refining AI-generated code</li> </ul> <p>For Cline these custom instructions can be defined through <strong>Rules</strong>.</p> <p>Rules are loaded when Cline starts a new task and are checked in the following order:</p> <ol> <li><code class="language-plaintext highlighter-rouge">.clinerules/</code> folder (all <code class="language-plaintext highlighter-rouge">.md</code> files inside)</li> <li>Single <code class="language-plaintext highlighter-rouge">.clinerules</code> file</li> <li><code class="language-plaintext highlighter-rouge">AGENTS.md</code> file</li> </ol> <p>This highlights that workspace rules override global rules when both define the same guidance.</p> <p>Rules provide consistent guidance for every interaction, but some tasks require more than general guidelines and would need step-by-step procedures. In addition, they are not necessarily invoked with every request. This is what skills are designed for.</p> <h3 id="skills">Skills</h3> <p>Skills are folders of instructions that package repeated workflows, specialized knowledge, or new capabilities for your agent. It contains a <strong>SKILL.md</strong> file, along with instructions, scripts, assets and resources in the same folder that agents can discover to perform a specific task accurately.</p> <p>The <strong>SKILL.md</strong> file contains 2 sections: metadata and instructions. The latter contains detailed instructions to follow for that particular skill. The metadata provides necessary information when to use this skill through specific fields of which 2 are mandatory: name and description.</p> <p>Agent skills are now an open standard and has been adopted by a growing number of agent products.</p> <p>Unlike rules which are always active, skills load on demand. Installing multiple skills does not affect the context as only the metadata is always loaded and the instructions and other files are loaded progressively when the skill is triggered.</p> <p>Context and instructions shape what the agent knows and how it behaves, but an agent also needs the ability to read files, execute commands, and interact with external systems. This is the role of tools.</p> <h2 id="tools-layer-what-the-agent-can-do">Tools Layer: What the Agent Can Do</h2> <h3 id="tools">Tools</h3> <p>Tools define what your AI assistant can do. They let an agent observe, decide and act in order to achieve goals.</p> <p>Common built-in tools generally include tools to interact with the file system like listing directories, reading and writing files. They also include web tools for searching the web as well as shell tools to execute commands.</p> <p>In order to extend the capabilities of the agent beyond common operations, agents use MCP servers, covered in the next section, also through a dedicated internal tool.</p> <h3 id="mcp">MCP</h3> <p>Model Context Protocol is an open-source standard for connecting AI applications to external systems. These systems include data sources such as local files and databases or tools like search engines. Through this standardization we reduce development time and complexity when integrating external systems and enhance the capabilities of our agents.</p> <p>Essentially MCP servers act as API that the models can use with predefined set of tools or functions that can be executed through this interface. They offer modular integration where they can be easily added or removed without overhauling the entire system architecture.</p> <p>With context management, persistent memory, custom instructions, skills, and extensible tools through MCP, we now have a complete picture of how modern AI coding agents work.</p> <p>With this we have reached the end of this post, I hope you enjoyed it!</p> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <p><em>Stay tuned!</em></p> <h2 id="recap">Recap</h2> <p>We have covered a lot of ground: from the basic agent building blocks to advanced features like context engineering, memory banks, MCP and Skills.</p> <p>This article is a snapshot of the current landscape focusing on foundational concepts and the challenges they address. The ecosystem is evolving rapidly, and there’s much we haven’t covered: sub-agents for delegating tasks, hooks for event-driven automation, workflows for chaining operations, and other patterns still taking shape.</p> <p>The tools will keep changing and the models will keep improving but the challenges that led to these patterns and concepts like context engineering, persisting knowledge and extending capabilities will enable you to use tools interchangeably as the landscape evolves.</p> <p><em>Happy learning!</em></p> <h2 id="resources">Resources</h2> <p><a href="https://cline.bot/blog/the-last-ai-coding-agent-youll-need">https://cline.bot/blog/the-last-ai-coding-agent-youll-need</a></p> <p><a href="https://newsletter.pragmaticengineer.com/p/two-years-of-using-ai">https://newsletter.pragmaticengineer.com/p/two-years-of-using-ai</a></p> <p><a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents">https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents</a></p> <p><a href="https://docs.cline.bot/prompting/understanding-context-management">https://docs.cline.bot/prompting/understanding-context-management</a></p> <p><a href="https://cline.bot/blog/memory-bank-how-to-make-cline-an-ai-agent-that-never-forgets">https://cline.bot/blog/memory-bank-how-to-make-cline-an-ai-agent-that-never-forgets</a></p> <p><a href="https://claude.com/blog/skills-explained">https://claude.com/blog/skills-explained</a></p> <p><a href="https://agentskills.io/home">https://agentskills.io/home</a></p> <p><a href="https://modelcontextprotocol.io/docs/getting-started/intro">https://modelcontextprotocol.io/docs/getting-started/intro</a></p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="AI Engineering"/><summary type="html"><![CDATA[Practicle guide on what are ai coding agents, best practices and features that extend their capabilities and how to leverage them using Cline]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/articles/30_prompt_engineering_vs_context_engineering.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/articles/30_prompt_engineering_vs_context_engineering.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Building Semantic Search for This Blog</title><link href="https://www.firasesbai.com/articles/2026/01/24/building-semantic-search.html" rel="alternate" type="text/html" title="Building Semantic Search for This Blog"/><published>2026-01-24T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2026/01/24/building-semantic-search</id><content type="html" xml:base="https://www.firasesbai.com/articles/2026/01/24/building-semantic-search.html"><![CDATA[<p><em>In this article, I will guide you on how I build and integrated semantic search using Google Cloud for my site.</em></p> <p>We all have seen the exponential adoption of Large Language Models (LLMs) since the release of ChatGPT on November 2022 and the headlines that promise us something new every year; from Model Context Protocol (MCP) and CLI Coding Agents to autonomous multi-agents systems and Context Graphs, the list goes on. Taking a step back, this article is my first attempt to show you how to build AI use cases that actually work. Building is a great way of learning while also gaining insights into how these systems work under the hood.</p> <p><em>So let’s get started!</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#what-is-semantic-search" id="markdown-toc-what-is-semantic-search">What is Semantic Search?</a></li> <li><a href="#the-architecture" id="markdown-toc-the-architecture">The Architecture</a></li> <li><a href="#tech-stack-choices" id="markdown-toc-tech-stack-choices">Tech Stack Choices</a></li> <li><a href="#challenges-and-optimizations" id="markdown-toc-challenges-and-optimizations">Challenges and Optimizations</a> <ul> <li><a href="#building-the-faiss-index" id="markdown-toc-building-the-faiss-index">Building the FAISS Index</a></li> <li><a href="#automatic-index-updates" id="markdown-toc-automatic-index-updates">Automatic Index Updates</a></li> <li><a href="#cold-start" id="markdown-toc-cold-start">Cold Start</a></li> <li><a href="#cost-optimization" id="markdown-toc-cost-optimization">Cost Optimization</a></li> </ul> </li> <li><a href="#whats-next" id="markdown-toc-whats-next">What’s Next?</a></li> </ul> <hr/> <h2 id="what-is-semantic-search">What is Semantic Search?</h2> <p>Semantic search is a way of finding information by meaning rather than exact keywords.<br/> Unlike traditional keyword search where the literal words in your query are matched to the same words in documents, semantic search converts both the query and the documents into vectors, known as <em>embeddings</em> that capture their meaning. In essence, it is a nearest-neighbor search problem where, given a query, the top most <em>semantically similar</em> items or vectors are retrieved based on some heuristic used to calculate the likelihood. The naive solution is k-nearest neighbors (k-NN), which in the context of our blog would follow these steps:</p> <ol> <li>Split all the blog posts into chunks, generate embeddings and save them into a vector store.</li> <li>Given a new search query, convert it to a vector embedding.</li> <li>Compute the similarity scores between the query embedding and all vectors in the database, using metrics such as cosine similarity.</li> <li>Rank all vectors by their similarity scores.</li> <li>Return <em>k</em> vectors with the highest similarity scores.</li> </ol> <h2 id="the-architecture">The Architecture</h2> <p>Before diving into implementation details, here’s the high-level architecture:</p> <div class="jekyll-diagrams diagrams mermaid"> <svg id="my-svg" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 501.422px; background-color: white;" viewBox="0 0 501.421875 578" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333}@keyframes edge-animation-frame{from{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round}#my-svg .error-icon{fill:#522}#my-svg .error-text{fill:#522;stroke:#522}#my-svg .edge-thickness-normal{stroke-width:1px}#my-svg .edge-thickness-thick{stroke-width:3.5px}#my-svg .edge-pattern-solid{stroke-dasharray:0}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none}#my-svg .edge-pattern-dashed{stroke-dasharray:3}#my-svg .edge-pattern-dotted{stroke-dasharray:2}#my-svg .marker{fill:#333;stroke:#333}#my-svg .marker.cross{stroke:#333}#my-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px}#my-svg p{margin:0}#my-svg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333}#my-svg .cluster-label text{fill:#333}#my-svg .cluster-label span{color:#333}#my-svg .cluster-label span p{background-color:transparent}#my-svg .label text,#my-svg span{fill:#333;color:#333}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#ececff;stroke:#9370db;stroke-width:1px}#my-svg .rough-node .label text,#my-svg .node .label text,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-anchor:middle}#my-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px}#my-svg .rough-node .label,#my-svg .node .label,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-align:center}#my-svg .node.clickable{cursor:pointer}#my-svg .root .anchor path{fill:#333!important;stroke-width:0;stroke:#333}#my-svg .arrowheadPath{fill:#333}#my-svg .edgePath .path{stroke:#333;stroke-width:2px}#my-svg .flowchart-link{stroke:#333;fill:none}#my-svg .edgeLabel{background-color:rgba(232,232,232,0.8);text-align:center}#my-svg .edgeLabel p{background-color:rgba(232,232,232,0.8)}#my-svg .edgeLabel rect{opacity:.5;background-color:rgba(232,232,232,0.8);fill:rgba(232,232,232,0.8)}#my-svg .labelBkg{background-color:rgba(232,232,232,0.5)}#my-svg .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#my-svg .cluster text{fill:#333}#my-svg .cluster span{color:#333}#my-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80,100%,96.2745098039%);border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#my-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333}#my-svg rect.text{fill:none;stroke-width:0}#my-svg .icon-shape,#my-svg .image-shape{background-color:rgba(232,232,232,0.8);text-align:center}#my-svg .icon-shape p,#my-svg .image-shape p{background-color:rgba(232,232,232,0.8);padding:2px}#my-svg .icon-shape rect,#my-svg .image-shape rect{opacity:.5;background-color:rgba(232,232,232,0.8);fill:rgba(232,232,232,0.8)}#my-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em}#my-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif}#my-svg .blue&gt;*{fill:#4285f4!important;stroke:#1a73e8!important;color:#fff!important}#my-svg .blue span{fill:#4285f4!important;stroke:#1a73e8!important;color:#fff!important}#my-svg .blue tspan{fill:#fff!important}#my-svg .orange&gt;*{fill:#f38020!important;stroke:#f38020!important;color:#fff!important}#my-svg .orange span{fill:#f38020!important;stroke:#f38020!important;color:#fff!important}#my-svg .orange tspan{fill:#fff!important}#my-svg .green&gt;*{fill:#34a853!important;stroke:#188038!important;color:#fff!important}#my-svg .green span{fill:#34a853!important;stroke:#188038!important;color:#fff!important}#my-svg .green tspan{fill:#fff!important}</style><g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"><g class="cluster" id="subGraph1" data-look="classic"><rect style="" x="8" y="314" width="307.140625" height="256"/><g class="cluster-label" transform="translate(112.6484375, 314)"><foreignObject width="97.84375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Google Cloud</p></span></div></foreignObject></g></g><g class="cluster" id="GitHub" data-look="classic"><rect style="" x="48.92578125" y="8" width="240.234375" height="232"/><g class="cluster-label" transform="translate(144.14453125, 8)"><foreignObject width="49.796875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>GitHub</p></span></div></foreignObject></g></g></g><g class="edgePaths"><path d="M169.043,87L169.043,93.167C169.043,99.333,169.043,111.667,169.043,123.333C169.043,135,169.043,146,169.043,151.5L169.043,157" id="L_Blog_Repo_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_Blog_Repo_0" data-points="W3sieCI6MTY5LjA0Mjk2ODc1LCJ5Ijo4N30seyJ4IjoxNjkuMDQyOTY4NzUsInkiOjEyNH0seyJ4IjoxNjkuMDQyOTY4NzUsInkiOjE2MX1d" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M169.043,215L169.043,219.167C169.043,223.333,169.043,231.667,169.043,242C169.043,252.333,169.043,264.667,169.043,277C169.043,289.333,169.043,301.667,169.043,311.333C169.043,321,169.043,328,169.043,331.5L169.043,335" id="L_Repo_AR_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_Repo_AR_0" data-points="W3sieCI6MTY5LjA0Mjk2ODc1LCJ5IjoyMTV9LHsieCI6MTY5LjA0Mjk2ODc1LCJ5IjoyNDB9LHsieCI6MTY5LjA0Mjk2ODc1LCJ5IjoyNzd9LHsieCI6MTY5LjA0Mjk2ODc1LCJ5IjozMTR9LHsieCI6MTY5LjA0Mjk2ODc1LCJ5IjozMzl9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M186.419,393L191.675,401.167C196.93,409.333,207.442,425.667,234.104,441.815C260.766,457.963,303.579,473.927,324.986,481.909L346.393,489.89" id="L_AR_External_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_AR_External_0" data-points="W3sieCI6MTg2LjQxODk0NTMxMjUsInkiOjM5M30seyJ4IjoyMTcuOTUzMTI1LCJ5Ijo0NDJ9LHsieCI6MzUwLjE0MDYyNSwieSI6NDkxLjI4Nzg0OTc1MDg2MjR9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M151.667,393L146.411,401.167C141.156,409.333,130.644,425.667,125.389,441.333C120.133,457,120.133,472,120.133,479.5L120.133,487" id="L_AR_CR_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_AR_CR_0" data-points="W3sieCI6MTUxLjY2Njk5MjE4NzUsInkiOjM5M30seyJ4IjoxMjAuMTMyODEyNSwieSI6NDQyfSx7IngiOjEyMC4xMzI4MTI1LCJ5Ijo0OTF9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel" transform="translate(169.04296875, 124)"><g class="label" data-id="L_Blog_Repo_0" transform="translate(-29.046875, -12)"><foreignObject width="58.09375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Triggers</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(169.04296875, 277)"><g class="label" data-id="L_Repo_AR_0" transform="translate(-42.25, -12)"><foreignObject width="84.5" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Build Image</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(217.953125, 442)"><g class="label" data-id="L_AR_External_0" transform="translate(-52.9140625, -24)"><foreignObject width="105.828125" height="48"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Fetch All Posts<br/>Build Index</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(120.1328125, 442)"><g class="label" data-id="L_AR_CR_0" transform="translate(-24.90625, -12)"><foreignObject width="49.8125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Deploy</p></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default green" id="flowchart-Blog-0" transform="translate(169.04296875, 60)"><rect class="basic label-container" style="fill:#34a853 !important;stroke:#188038 !important" x="-82.46875" y="-27" width="164.9375" height="54"/><g class="label" style="color:#fff !important" transform="translate(-52.46875, -12)"><rect/><foreignObject width="104.9375" height="24"><div style="color: rgb(255, 255, 255) !important; display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>New Blog Post</p></span></div></foreignObject></g></g><g class="node default green" id="flowchart-Repo-1" transform="translate(169.04296875, 188)"><rect class="basic label-container" style="fill:#34a853 !important;stroke:#188038 !important" x="-85.1171875" y="-27" width="170.234375" height="54"/><g class="label" style="color:#fff !important" transform="translate(-55.1171875, -12)"><rect/><foreignObject width="110.234375" height="24"><div style="color: rgb(255, 255, 255) !important; display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>CI/CD workflow</p></span></div></foreignObject></g></g><g class="node default blue" id="flowchart-CR-2" transform="translate(120.1328125, 518)"><rect class="basic label-container" style="fill:#4285f4 !important;stroke:#1a73e8 !important" x="-67.8046875" y="-27" width="135.609375" height="54"/><g class="label" style="color:#fff !important" transform="translate(-37.8046875, -12)"><rect/><foreignObject width="75.609375" height="24"><div style="color: rgb(255, 255, 255) !important; display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>Cloud Run</p></span></div></foreignObject></g></g><g class="node default blue" id="flowchart-AR-3" transform="translate(169.04296875, 366)"><rect class="basic label-container" style="fill:#4285f4 !important;stroke:#1a73e8 !important" x="-86.4609375" y="-27" width="172.921875" height="54"/><g class="label" style="color:#fff !important" transform="translate(-56.4609375, -12)"><rect/><foreignObject width="112.921875" height="24"><div style="color: rgb(255, 255, 255) !important; display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>Artifact Registry</p></span></div></foreignObject></g></g><g class="node default orange" id="flowchart-External-4" transform="translate(421.78125, 518)"><rect class="basic label-container" style="fill:#f38020 !important;stroke:#f38020 !important" x="-71.640625" y="-27" width="143.28125" height="54"/><g class="label" style="color:#fff !important" transform="translate(-41.640625, -12)"><rect/><foreignObject width="83.28125" height="24"><div style="color: rgb(255, 255, 255) !important; display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>My Website</p></span></div></foreignObject></g></g></g></g></g></svg> </div> <p style="text-align:center;">Figure 1: High Level Architecture</p> <p><strong>The flow is simple:</strong></p> <ol> <li>When I publish a new blog post, a GitHub workflow is triggered to rebuild the image of the backend serving the semantic search endpoint.</li> <li>Building the image involves fetching posts from the website, generating embeddings, and building a search index</li> <li>The image is pushed to Artifact Registry and deployed to Cloud Run</li> </ol> <p>With the high level flow defined, in the following sections we look in more details into the constraints that influenced the tech stack choices, faced challenges and implemented optimizations.</p> <h2 id="tech-stack-choices">Tech Stack Choices</h2> <p>With the objective of keeping cost to a minimum, yet still having fast deployments, minimal operations and easily scalable solution, choosing a fully managed serverless platform for deploying the backend service became the clear answer. In addition, the generous free tier of Google Cloud made the decision easier to go with <strong>Cloud Run</strong>. Cloud Run enables you to run stateless containers on top of Google’s scalable infrastructure. <br/> The free tier includes 2 million requests/month and enough memory and compute time for the amount of traffic that I’m expecting. Crossing these limits would only cost pennies on the dollar given that you only pay for what you use and you can always scale it down to zero. For more information on free tier usage limits, you can check <a href="https://docs.cloud.google.com/free/docs/free-cloud-features#cloud-run">this page</a>.</p> <p>Considering the choice of Cloud Run and the number of blog posts and expected size of the generated embeddings, I decided to go with an in-memory vector database. <strong>FAISS</strong> is Facebook’s similarity search library. It is fast, in-memory and suitable for this scale. FAISS builds an index for fast similarity search and instead of comparing the query to every post which can be slow, it uses smart data structures to find the nearest neighbors quickly. Another required component that goes hand in hand with this is an embedding model. I used <strong>sentence-transformers</strong> library. It is a python framework for accessing and using state of the art embedding models. In addition, to keep the image size reduced, I used the<code class="language-plaintext highlighter-rouge">all-MiniLM-L6-v2</code> model which is around 90MB and generates 384-dimensional vectors.</p> <p>Regarding the backend, I used <strong>FastAPI</strong>; modern, fast and high-performance web framework for building python based APIs with automatic API docs, and excellent type safety with Pydantic.</p> <h2 id="challenges-and-optimizations">Challenges and Optimizations</h2> <h3 id="building-the-faiss-index">Building the FAISS Index</h3> <p>In order to have faster container startup, I build the FAISS index during Docker image creation, not at runtime. This ensures consistent results across all instances since the filesystem is ephemeral and no external dependencies at runtime.</p> <h3 id="automatic-index-updates">Automatic Index Updates</h3> <p>We keep the blog and the search backend in separate repositories to keep concerns isolated and releases simple. The open question was: how do we update the search index whenever a new post goes live?</p> <p>To solve this, I added a GitHub Actions workflow in the blog repo that triggers on publishing and calls the GitHub REST API to send a <code class="language-plaintext highlighter-rouge">repository_dispatch</code> event to the backend repo with a custom <code class="language-plaintext highlighter-rouge">event_type=blog-updated</code>. The backend repo has a workflow that listens for that event; when it matches, it builds and deploys the Docker image and refreshes the index.</p> <h3 id="cold-start">Cold Start</h3> <p>When Cloud Run scales from zero, it takes between 5 and 10 seconds to start a container. This time includes the container startup time and loading of the embedding model as well as the FAISS index.</p> <p>Cloud Run has the option to allocate extra CPU during startup and which can be activated by this flag:</p> <p><code class="language-plaintext highlighter-rouge">--cpu-boost</code></p> <p>In comparison to the size of the FAISS index, loading the embedding model, even a small one, on every cold start is slow. One solution to this would be caching the model to a specific location inside the container by adding the following line in your Dockerfile:</p> <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">      
    <span class="no">RUN</span> <span class="n">mkdir</span> <span class="o">-</span><span class="nb">p</span> <span class="sr">/app/mo</span><span class="n">dels</span> <span class="o">&amp;&amp;</span> <span class="p">\</span>
    <span class="sr">/app/</span><span class="p">.</span><span class="nf">venv</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">python</span> <span class="o">-</span><span class="n">c</span> <span class="s2">"from sentence_transformers import SentenceTransformer; model = SentenceTransformer('all-MiniLM-L6-v2'); model.save('/app/models/all-MiniLM-L6-v2')"</span>
   
   </code></pre></figure> <p>While keeping the minimum number of running instances set to 1 would solve the issue of the cold start completely, it would also result in extra costs. Depending on your traffic and usage of the search functionality in your system, you can opt for this alternative. For me the objective was to build and test the sematic search end to end and even though I could still face timeout errors occasionally when searching it is still acceptable for now.</p> <h3 id="cost-optimization">Cost Optimization</h3> <p>Since Cloud Run charges for the resources you use and in order not incur additional costs after the free tier limits, the service shuts down and scales back to zero when there’s no traffic and the Cloud Run service is idle. However, each time a new post is published, this results in a new image version. To avoid storage costs and stay within the free tier limit of Artifact Registry which is &lt; 0.5 GB, I have applied the following cleanup policy to keep only the last 2 docker images:</p> <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">      
    <span class="p">{</span>
        <span class="s2">"name"</span><span class="p">:</span> <span class="s2">"keep-recent-2"</span><span class="p">,</span>
        <span class="s2">"action"</span><span class="p">:</span> <span class="s2">"DELETE"</span><span class="p">,</span>
        <span class="s2">"condition"</span><span class="p">:</span> <span class="p">{</span>
            <span class="s2">"tagState"</span><span class="p">:</span> <span class="s2">"UNTAGGED"</span><span class="p">,</span>
            <span class="s2">"newerThan"</span><span class="p">:</span> <span class="s2">"0s"</span>
        <span class="p">},</span>
        <span class="s2">"mostRecentVersions"</span><span class="p">:</span> <span class="p">{</span>
            <span class="s2">"keepCount"</span><span class="p">:</span> <span class="mi">2</span>
        <span class="p">}</span>
    <span class="p">}</span>
   
   </code></pre></figure> <h2 id="whats-next">What’s Next?</h2> <p>Building a semantic search doesn’t require expensive infrastructure. With open-source tools and serverless platforms, you can build production-quality services that scale and cost almost nothing. Having the core architecture in place, adding new use cases such as a recommendations endpoint based on the existing similarity search and vector database or potentially combining the semantic search with keyword matching for better results are now easier. Avoiding over engineered solutions and solving the problems as they araise avoids pre mature unnecessary optimizations.</p> <p>I’d love to hear your thoughts and know more about what AI projects you’re currently working on.</p> <p>With this we have reached the end of this post, I hope you enjoyed it!</p> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <p><em>Stay tuned!</em></p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="AI Engineering"/><summary type="html"><![CDATA[Guide on how to build a semantic search using fastapi, faiss and sentence-transformers on google cloud]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/default-seo-tag-image.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/default-seo-tag-image.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Data Platform Engineering</title><link href="https://www.firasesbai.com/articles/2026/01/11/data-platform-engineering.html" rel="alternate" type="text/html" title="Data Platform Engineering"/><published>2026-01-11T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2026/01/11/data-platform-engineering</id><content type="html" xml:base="https://www.firasesbai.com/articles/2026/01/11/data-platform-engineering.html"><![CDATA[<p><em>In this article, we dive into data platforms, their components and the impact of AI with the rise of large language models and autonomous agents.</em></p> <p>In the data engineering capabilities and personas’ <a href="https://www.firasesbai.com/articles/2025/05/25/data-engineering-capabilities-and-personas.html">article</a>, we looked at some emerging constellations of data teams and the general responsibilities associated with them. One area we mentioned was data platform engineering. While having already touched on some aspects needed for building a data platform, we will attempt in this article to further explore this topic starting with what exactly is a data platform and where AI will likely play a role in it.</p> <p><em>So let’s get started!</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#what-is-a-data-platform" id="markdown-toc-what-is-a-data-platform">What is a Data Platform?</a></li> <li><a href="#layers-of-a-data-platform" id="markdown-toc-layers-of-a-data-platform">Layers of a Data Platform</a> <ul> <li><a href="#ingestion-layer" id="markdown-toc-ingestion-layer">Ingestion Layer</a></li> <li><a href="#storage-layer" id="markdown-toc-storage-layer">Storage Layer</a></li> <li><a href="#processing-layer" id="markdown-toc-processing-layer">Processing Layer</a></li> <li><a href="#consumption-layer" id="markdown-toc-consumption-layer">Consumption Layer</a></li> <li><a href="#intelligence-layer" id="markdown-toc-intelligence-layer">Intelligence Layer</a></li> </ul> </li> <li><a href="#benefits-of-a-data-platform" id="markdown-toc-benefits-of-a-data-platform">Benefits of a Data Platform</a></li> <li><a href="#recap" id="markdown-toc-recap">Recap</a></li> <li><a href="#resources" id="markdown-toc-resources">Resources</a></li> </ul> <hr/> <h2 id="what-is-a-data-platform">What is a Data Platform?</h2> <p>For a variety of reasons, main one being marketing, the term “<em>platform</em>” has become overloaded with different meanings in different contexts and the term data platform is no exception.</p> <p>The Data Warehouse was probably the first attempt at a centralized data platform. As we have seen <a href="https://www.firasesbai.com/articles/2024/11/17/evolution-analytical-data-architectures.html">here</a>, things evolved in the big data era and the concept of the data lake emerged as a response to the previous limitations. A modern version or the modern data stack as it is often referenced to, represents a shift from focusing on a single technology to focusing on the entire data lifecycle. It is not a single tool, but an integrated ecosystem of tools designed to cover every step of the data flow.</p> <p>Another natural evolution would then be the self-service data platform. Data engineering teams suffering through many <strong><em>“can you quickly pull this data for me?”</em></strong> kind of requests, the self service aspect is a process that makes it easier for the end users looking to “<em>quickly</em>” access some data to find it, understand its meaning and how they can use it and remove them from the process to some degree. This will enable non-technical users like marketing managers and sales leads to build their own views, filter their own data, and answer their own questions.</p> <picture> <source srcset="/assets/images/articles/29_data_platform_engineering_meme.webp" type="image/webp"/> <img src="/assets/images/articles/29_data_platform_engineering_meme.png" alt="quickly pull this data for me meme" loading="lazy"/> </picture> <p><br/></p> <p>Before looking into the components of a data platform, other variations that would fit under the umbrella concept of the <strong><em>“data platform”</em></strong> include:</p> <ul> <li><strong>Data Stack</strong>: a collection of various tools wired together to perform the platform’s functions.</li> <li><strong>Data Fabric</strong>: A unifying technology layer that provides centralized access and governance across data that is <em>physically</em> stored in many different places.</li> <li><strong>Customer Data Platform</strong>: A platform specifically focused on consolidating and unifying all customer data (sales, web, app, email) to create a single, actionable “360-degree view” of a customer.</li> </ul> <h2 id="layers-of-a-data-platform">Layers of a Data Platform</h2> <p>One way to think about the components of a data platform is through layers. Each layer will contain a set of tools that, stacked together cover the end to end lifecycle of data.</p> <h3 id="ingestion-layer">Ingestion Layer</h3> <p>This is the starting point of any data platform. Data within an organization is usually scattered across multiple systems and the objective is to consolidate it into one place. This usually starts with identifying the required data sources and how to access them. Patterns such as Change Data Capture (CDC) and automated pipelines would be part of this layer copying new data and moving it to the next storage layer.</p> <h3 id="storage-layer">Storage Layer</h3> <p>The storage layer provides a centralized and scalable way for storing and managing the data in a way that it can be accessed, queried, and updated easily. Choosing the right storage technology and data models are key considerations to keep in mind. The decision will also depend on the type of data, volume, size and maturity of the organization. Options include data warehouses, data lakes and recently the rising adoption of data lakehouses. It is worth mentioning that it’s not unusual to find organizations that have a combination of multiple data management solutions at once.</p> <h3 id="processing-layer">Processing Layer</h3> <p>While it might be tricky to draw a clear boundary around this layer, the main purpose is usually preparing data by executing a set of transformations on it. It can be thought of as an enabler to the other layers and hence used multiple times across the data lifecycle especially as we get closer to the consumption layer where the data needs to be accessible and easily consumable by different teams each having a different set of requirements. Therefore, similar to the ingestion layer for example, we also find complex data pipelines and orchestration tools to automate, manage and monitor the flow of data and execution of transformations.<br/> Depending on which technique is used, this layer represents the <strong>T</strong> in ETL/ELT patterns. These transformations include data cleaning such as removing errors or duplicates, schema validation and other data quality checks, normalization, aggregation and enrichment with other sources.</p> <h3 id="consumption-layer">Consumption Layer</h3> <p>This is where the business value happens. The consumption of the data happens in different forms because the users of this layer can be both technical and non technical. For technical personas, they would probably look for new datasets to enrich their models with new features. Others would be interested in a dashboard that helps them gain insights into trends and patterns or review certain KPIs to make informed decisions. Another form of presenting the data is through reports that are intended for a specific type of stakeholders looking for a structured and easy to understand format. In this layer, the problem of your data engineering team becoming the bottleneck as we mentioned earlier manifests the most. Typically, the process involves submitting a new request for a report to the data team. You wait two weeks. They bring it to you. If you want to change a filter, you create a new request and wait again. However, the modern self service approach leverages governance to make the data accessible and ready for consumption making the feedback loop shorter.</p> <h3 id="intelligence-layer">Intelligence Layer</h3> <p>This is the biggest shift in the last two or three years. With the rise of Large Language Models (LLMs) and autonomous AI Agents, new use cases have emerged and are now being integrated across the different layers of the data platform. Yet, in my opinion, it still makes sense to reason about them as a logically separate layer.</p> <p>One of the patterns that have matured and became a default is <strong>Retrieval-Augmented Generation</strong> or shortly <strong>RAG</strong>. While LLMs are trained on large volumes of data and can generate an output for a variety of tasks, their responses will not be as effective against questions related to an organization’s internal information. This data is proprietary and not publicly available to be included in the training data which leads the LLMs to hallucinate and make up things in an attempt to provide an answer. RAG is a cost effective approach to overcome this limitation and improve outputs by providing accurate, relevant answers grounded in internal data.</p> <p>On a high level, RAG works as follows:</p> <ol> <li><strong>Retrieval</strong>: When a user asks a question, the system first searches an external knowledge base (e.g., documents, databases) for relevant information.</li> <li><strong>Augmentation</strong>: The retrieved information is then added to the original prompt.</li> <li><strong>Generation</strong>: The LLM uses this combined information (original prompt + retrieved facts) to generate a more informed and factual response.</li> </ol> <p>Going deeper on this pattern is not the scope of this article, and we must highlight that this is obviously a simplified explanation and rather basic as things have evolved over time but still needed to understand its implications from a data platform perspective. Making the enterprise knowledge <em>RAG-ready</em> introduces new pipelines: fetching documents, splitting them into small chunks, generating embeddings and saving them into vector databases are all required steps that are increasingly becoming platform native. As RAG matures, adoption of vector databases is accelerating and enterprises are adding vector-native storage and search features to enable high performance and scalable AI applications.</p> <p>Beyond RAG powered chat assistants, enterprises are also shifting towards <strong>agents</strong> that plan, call tools/APIs, and execute workflows autonomously. In generic terms, agents can be defined as LLM powered systems that have access to a set of tools that allow them to take actions in a certain environments. Therefore, RAG based assistant can be considered as an agent with a single tool that is the retriever part that searches and fetches relevant documents depending on the user query. Now that you can quickly search and interact with your documentation, you want to give your system access to your project management tool. This allows you to fetch ongoing issues related to your discussion, check their status and create follow up issues if needed. You can then deploy an autonomous bot that can answer support requests, provide links to relevant documentation, check systems’ health and file bugs. These systems will continue to improve over time and become more capable but we should always bear in mind the risks that come with building a system where AI makes decisions automatically. That’s why human‑in‑the‑loop is non‑negotiable for high‑impact actions taken by the agents as well as the importance of platform observability. Moving beyond established capabilities, we need to also integrate agent specific monitoring. Things like understanding their interactions with external tools, RAG performance, multi-step agent execution, tracing user queries and analyzing model responses will help ensure reliability, debug issues and optimize costs for these complex AI pipelines.</p> <p>Another clear trend is the move toward semantic, natural‑language access. Business users ask questions in plain language; assistants translate that into SQL, run governed queries, and return answers with citations. Modern platforms now embed this inside the warehouse/lakehouse and BI experiences, inheriting your RBAC/masking policies. This shortens feedback loops without bypassing governance.</p> <p>These are some examples of new patterns and trends that I believe are probably here to stay. However, things will continue to evolve and we should expect more changes affecting all the layers of the data platform.</p> <h2 id="benefits-of-a-data-platform">Benefits of a Data Platform</h2> <p>The main benefit of a data platform is to enable other teams. Your data team can’t grow as fast as your data and the former will quickly become a bottleneck if every question requires a dedicated data engineer. In addition, by handling common laborious tasks at the platform layer such as infrastructure setup and governance, teams can innovate and iterate more quickly. This will result in self perpetuation of the platform by making data quickly available for other teams to consume and generate value from.</p> <p>In order to achieve this, the platform team needs to focus on the onboarding process making it easy for the other teams to join the platform. They need to take into consideration the maturity levels spectrum across the organization and account for when to enforce constraints and when to lower the entrance barrier. Faced with these challenges, a common pitfall is tool fatigue. It’s easy to buy 15 different “best-in-class” tools that don’t talk to each other trying to cover every aspect and accommodating every need in order to build the perfect platform. Depending on the platform adoption and perceived benefits, it will be very difficult to justify these costs down the line. The platform team should instead start small, gather user feedback and iterate quickly favoring simplicity over complex and over engineered solutions.</p> <p>With this we have reached the end of this post, I hope you enjoyed it!</p> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <p><em>Stay tuned!</em></p> <h2 id="recap">Recap</h2> <p>Building a data platform is a journey. Teams should start small and aim to getting data out of silos and into a place where other teams can actually use it. The goal isn’t to have the fanciest technology. The goal is to stop arguing about <em>whose</em> spreadsheet is right, and start making better decisions.</p> <p><em>Happy learning!</em></p> <h2 id="resources">Resources</h2> <p><a href="https://www.startdataengineering.com/post/self-serve-data-platform/">https://www.startdataengineering.com/post/self-serve-data-platform/</a></p> <p><a href="ttps://www.confessionsofadataguy.com/building-data-platforms-from-scratch/">ttps://www.confessionsofadataguy.com/building-data-platforms-from-scratch/</a></p> <p><a href="https://developer.nvidia.com/blog/building-scalable-ai-on-enterprise-data-with-nvidia-nemotron-rag-and-microsoft-sql-server-2025/">https://developer.nvidia.com/blog/building-scalable-ai-on-enterprise-data-with-nvidia-nemotron-rag-and-microsoft-sql-server-2025/</a></p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="Data Engineering"/><category term="AI Engineering"/><summary type="html"><![CDATA[modern data platform engineering, components, benefits and impact of AI including LLMs and agents.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/articles/29_data_platform_engineering_meme.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/articles/29_data_platform_engineering_meme.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Data Engineering Design Patterns</title><link href="https://www.firasesbai.com/articles/2025/11/13/data-engineering-design-patterns.html" rel="alternate" type="text/html" title="Data Engineering Design Patterns"/><published>2025-11-13T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2025/11/13/data-engineering-design-patterns</id><content type="html" xml:base="https://www.firasesbai.com/articles/2025/11/13/data-engineering-design-patterns.html"><![CDATA[<p><em>In this article, we will look at common data engineering design patterns.</em></p> <p>Design patterns are standard solutions to common problems. They represent best practices and templates that can be reused in multiple situations. In this article, we will explore four common patterns that can be leveraged to ensure reliability, performance and observability in data pipelines.</p> <p><em>So let’s get started!</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#write-audit-publish-wap" id="markdown-toc-write-audit-publish-wap">Write-Audit-Publish (WAP)</a></li> <li><a href="#change-data-capture-cdc" id="markdown-toc-change-data-capture-cdc">Change Data Capture (CDC)</a> <ul> <li><a href="#log-based-cdc" id="markdown-toc-log-based-cdc">Log-based CDC</a></li> <li><a href="#trigger-based-cdc" id="markdown-toc-trigger-based-cdc">Trigger-based CDC</a></li> <li><a href="#timestamp-based-cdc" id="markdown-toc-timestamp-based-cdc">Timestamp-based CDC</a></li> </ul> </li> <li><a href="#dead-letter-queue-dlq" id="markdown-toc-dead-letter-queue-dlq">Dead-Letter Queue (DLQ)</a></li> <li><a href="#cumulative-aggregate-table" id="markdown-toc-cumulative-aggregate-table">Cumulative Aggregate Table</a></li> <li><a href="#recap" id="markdown-toc-recap">Recap</a></li> <li><a href="#resources" id="markdown-toc-resources">Resources</a></li> </ul> <hr/> <h2 id="write-audit-publish-wap">Write-Audit-Publish (WAP)</h2> <p>Weather it is a schema evolution in the upstream data or third party APIs or incorrect join clause in your pipeline, preventing the resulting bad data and ensuring data integrity when writing to production systems requires rigorous validation.</p> <p>The WAP (Write-Audit-Publish) pattern is a critical data strategy that prevents such data quality issues. This method involves writing data to a staging table, auditing it for quality, and only publishing it to production if it passes checks. This ensures that the end users or consumers of this data can trust it.</p> <div class="jekyll-diagrams diagrams mermaid"> <svg id="my-svg" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 518.008px; background-color: white;" viewBox="0 0 518.0078125 582" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333}@keyframes edge-animation-frame{from{stroke-dashoffset:0}}@keyframes dash{to{stroke-dashoffset:0}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round}#my-svg .error-icon{fill:#522}#my-svg .error-text{fill:#522;stroke:#522}#my-svg .edge-thickness-normal{stroke-width:1px}#my-svg .edge-thickness-thick{stroke-width:3.5px}#my-svg .edge-pattern-solid{stroke-dasharray:0}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none}#my-svg .edge-pattern-dashed{stroke-dasharray:3}#my-svg .edge-pattern-dotted{stroke-dasharray:2}#my-svg .marker{fill:#333;stroke:#333}#my-svg .marker.cross{stroke:#333}#my-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px}#my-svg p{margin:0}#my-svg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333}#my-svg .cluster-label text{fill:#333}#my-svg .cluster-label span{color:#333}#my-svg .cluster-label span p{background-color:transparent}#my-svg .label text,#my-svg span{fill:#333;color:#333}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#ececff;stroke:#9370db;stroke-width:1px}#my-svg .rough-node .label text,#my-svg .node .label text,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-anchor:middle}#my-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px}#my-svg .rough-node .label,#my-svg .node .label,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-align:center}#my-svg .node.clickable{cursor:pointer}#my-svg .root .anchor path{fill:#333!important;stroke-width:0;stroke:#333}#my-svg .arrowheadPath{fill:#333}#my-svg .edgePath .path{stroke:#333;stroke-width:2px}#my-svg .flowchart-link{stroke:#333;fill:none}#my-svg .edgeLabel{background-color:rgba(232,232,232,0.8);text-align:center}#my-svg .edgeLabel p{background-color:rgba(232,232,232,0.8)}#my-svg .edgeLabel rect{opacity:.5;background-color:rgba(232,232,232,0.8);fill:rgba(232,232,232,0.8)}#my-svg .labelBkg{background-color:rgba(232,232,232,0.5)}#my-svg .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#my-svg .cluster text{fill:#333}#my-svg .cluster span{color:#333}#my-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80,100%,96.2745098039%);border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#my-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333}#my-svg rect.text{fill:none;stroke-width:0}#my-svg .icon-shape,#my-svg .image-shape{background-color:rgba(232,232,232,0.8);text-align:center}#my-svg .icon-shape p,#my-svg .image-shape p{background-color:rgba(232,232,232,0.8);padding:2px}#my-svg .icon-shape rect,#my-svg .image-shape rect{opacity:.5;background-color:rgba(232,232,232,0.8);fill:rgba(232,232,232,0.8)}#my-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em}#my-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif}</style><g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"/><g class="edgePaths"><path d="M259.004,62L259.004,66.167C259.004,70.333,259.004,78.667,259.004,86.333C259.004,94,259.004,101,259.004,104.5L259.004,108" id="L_A_B_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_A_B_0" data-points="W3sieCI6MjU5LjAwMzkwNjI1LCJ5Ijo2Mn0seyJ4IjoyNTkuMDAzOTA2MjUsInkiOjg3fSx7IngiOjI1OS4wMDM5MDYyNSwieSI6MTEyfV0=" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M259.004,166L259.004,170.167C259.004,174.333,259.004,182.667,259.004,190.333C259.004,198,259.004,205,259.004,208.5L259.004,212" id="L_B_C_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_B_C_0" data-points="W3sieCI6MjU5LjAwMzkwNjI1LCJ5IjoxNjZ9LHsieCI6MjU5LjAwMzkwNjI1LCJ5IjoxOTF9LHsieCI6MjU5LjAwMzkwNjI1LCJ5IjoyMTZ9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M196.91,294L187.091,300.167C177.273,306.333,157.637,318.667,147.818,330.333C138,342,138,353,138,358.5L138,364" id="L_C_D_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_C_D_0" data-points="W3sieCI6MTk2LjkwOTc5NjQ2MzgxNTc4LCJ5IjoyOTR9LHsieCI6MTM4LCJ5IjozMzF9LHsieCI6MTM4LCJ5IjozNjh9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M321.098,294L330.916,300.167C340.735,306.333,360.371,318.667,370.19,332.333C380.008,346,380.008,361,380.008,368.5L380.008,376" id="L_C_F_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_C_F_0" data-points="W3sieCI6MzIxLjA5ODAxNjAzNjE4NDIsInkiOjI5NH0seyJ4IjozODAuMDA3ODEyNSwieSI6MzMxfSx7IngiOjM4MC4wMDc4MTI1LCJ5IjozODB9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M380.008,434L380.008,440.167C380.008,446.333,380.008,458.667,380.008,468.333C380.008,478,380.008,485,380.008,488.5L380.008,492" id="L_F_G_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_F_G_0" data-points="W3sieCI6MzgwLjAwNzgxMjUsInkiOjQzNH0seyJ4IjozODAuMDA3ODEyNSwieSI6NDcxfSx7IngiOjM4MC4wMDc4MTI1LCJ5Ijo0OTZ9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" data-id="L_A_B_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_B_C_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(138, 331)"><g class="label" data-id="L_C_D_0" transform="translate(-17.7890625, -12)"><foreignObject width="35.578125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Pass</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(380.0078125, 331)"><g class="label" data-id="L_C_F_0" transform="translate(-12.890625, -12)"><foreignObject width="25.78125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Fail</p></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_F_G_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" id="flowchart-A-0" transform="translate(259.00390625, 35)"><rect class="basic label-container" style="fill:#e0f7fa !important;stroke:#00bcd4 !important" x="-91.796875" y="-27" width="183.59375" height="54"/><g class="label" style="" transform="translate(-61.796875, -12)"><rect/><foreignObject width="123.59375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>New Data Arrives</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-B-1" transform="translate(259.00390625, 139)"><rect class="basic label-container" style="" x="-107.9765625" y="-27" width="215.953125" height="54"/><g class="label" style="" transform="translate(-77.9765625, -12)"><rect/><foreignObject width="155.953125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Write to Staging Table</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-C-3" transform="translate(259.00390625, 255)"><rect class="basic label-container" style="" x="-130" y="-39" width="260" height="78"/><g class="label" style="" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel"><p>Perform Data Quality Checks</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-D-5" transform="translate(138, 407)"><rect class="basic label-container" style="fill:#c8e6c9 !important;stroke:#4caf50 !important" x="-130" y="-39" width="260" height="78"/><g class="label" style="" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel"><p>Write Data to Production Table</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-F-7" transform="translate(380.0078125, 407)"><rect class="basic label-container" style="fill:#ffe0b2 !important;stroke:#ff9800 !important" x="-62.0078125" y="-27" width="124.015625" height="54"/><g class="label" style="" transform="translate(-32.0078125, -12)"><rect/><foreignObject width="64.015625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Fire Alert</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-G-9" transform="translate(380.0078125, 535)"><rect class="basic label-container" style="" x="-130" y="-39" width="260" height="78"/><g class="label" style="" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel"><p>Manually Troubleshoot DQ Issue</p></span></div></foreignObject></g></g></g></g></g></svg> </div> <p style="text-align:center;">Figure 1: Write Audit Publish Workflow</p> <p>In order to understand how the WAP pattern can be implemented we will use a key feature of the open table format Apache Iceberg: <a href="https://iceberg.apache.org/docs/1.6.1/branching/">Branches</a>.<br/> For more information on Apache Iceberg and how it compares to the other well known formats, you can check <a href="https://www.firasesbai.com/articles/2024/11/17/evolution-analytical-data-architectures.html">this article</a>.</p> <p>The metadata of an Iceberg table stores a history of snapshots. These snapshots are changes applied to the table and are the basis for reader isolation and time travel. Iceberg branches are another named reference to a snapshot of a table. They can be used for handling GDPR requirements and retaining important historical snapshots for auditing or be part of an ETL pipeline enabling validation of new incoming data.</p> <p>Implementing the 3 steps WAP pattern using Iceberg branches would result in the following:</p> <ol> <li><strong>Write</strong>: switch branch from <strong><em>main</em></strong> to an <strong><em>audit</em></strong> branch and commits updates there. Data is not yet accessible to downstream users who can only access main branch.</li> <li><strong>Audit</strong>: Run data quality checks on the audit branch.</li> <li><strong>Publish</strong>: The <strong><em>main</em></strong> branch can be <code class="language-plaintext highlighter-rouge">fastForward</code> to the head of the audit <strong><em>branch</em></strong> to update the main table state.</li> </ol> <figure> <picture> <source srcset="/assets/images/articles/28_wap_iceberg_audit_branch.webp" type="image/webp"/> <img src="/assets/images/articles/28_wap_iceberg_audit_branch.png" alt="example diagram of audit branch" loading="lazy"/> </picture> <figcaption>Figure 2: Example Diagram of Audit Branch - <a href="https://iceberg.apache.org/docs/1.6.1/branching/#audit-branch">Image Source</a></figcaption> </figure> <p>Using the WAP pattern, downstream pipelines or dashboards can intuitively depend on the production table directly. However, the main challenges is the introduced delays and increased latency by the multistep process which might not be idea for near real time use cases.</p> <h2 id="change-data-capture-cdc">Change Data Capture (CDC)</h2> <p>Change Data Capture (CDC) is a data integration pattern that captures all changes in a source database; creates, updates and deletes and makes them available for downstream systems.</p> <p>Organizations typically have a variety of operational data spread across different systems and applications needed for running the business. The purpose of CDC is to keep this data synchronized by capturing changes from source databases and moving it to your target data warehouse or data lake.</p> <p>In contrast to batch processing, where a pipeline is scheduled periodically to replicate data from one system to another, CDC is considered more efficient. It eliminates the bulk load during specified windows and enables an incremental loading making the data available in near real time. In addition, it reduces the impact on the source database</p> <p>CDC is typically implemented using two main approaches: <strong>push</strong> and <strong>pull</strong>. In the push approach, the source database pushes the updates to the downstream systems. This has the advantage that the target systems have the latest data in near real time. In the pull approach on the other hand, the source database only logs the changes and it is the responsibility of the target systems to continuously poll it. This results in a delay of the data availability. In both approaches, if the source or target systems are not available for some reason, the data is lost. To overcome this, a messaging system can be used in between to buffer these changes.</p> <p>There are 3 common methods for how changes in data are detected using CDC:</p> <ul> <li>Log-based</li> <li>Timestamp-based</li> <li>Trigger-based</li> </ul> <h3 id="log-based-cdc">Log-based CDC</h3> <p>Transactional databases have internal log files where they record all changes committed against the database. These log files are primarily used for backup and disaster recovery purposes. By reading from these transaction logs we can propagate the changes to target systems without adding computational overhead. However, since each vendor’s logs have a different format, this method can’t be easily reusable.</p> <h3 id="trigger-based-cdc">Trigger-based CDC</h3> <p>Another feature that is widely supported by databases is trigger functions. These are stored procedures that are automatically executed once a specific event occurs on a table. We need to create per table one trigger for each operation and store the data changes in a separate table. This adds additional write operations and might impact the performance of the database. Also managing a large number of triggers can become challenging.</p> <h3 id="timestamp-based-cdc">Timestamp-based CDC</h3> <p>This method requires changes to the database schema to include a timestamp column to record when was an entry last updated. Changes are captured by selecting items with a timestamp newer than the previous check timestamp. Being probably the easiest to implement, it fails at capturing delete operations and could add an additional overhead to the database.</p> <h2 id="dead-letter-queue-dlq">Dead-Letter Queue (DLQ)</h2> <p>In streaming environments, a dead-letter queue is a special message queue used to store messages that were not successfully processed by the primary system. These include corrupted messages due to network or system failure or ones that failed the data quality checks.</p> <p>By having a DLQ in place, we provide a mechanism for analyzing and identifying common patterns of errors enabling better observability in order to improve the reliability of the system. In addition, we can reduce the data loss by reprocessing messages that couldn’t be delivered from the DLQ.</p> <h2 id="cumulative-aggregate-table">Cumulative Aggregate Table</h2> <p>The cumulative aggregate table design pattern is very useful when we want to solve the performance issue that comes with calculating aggregates over a large rolling time window.</p> <p>For example, if we want to calculate the number of active users for the last 30-days window, the straightforward solution would be to write a query that scans <em>30 days of raw event data for <strong>all users</strong> every single day</em>. This query is slow and expensive especially if run regularly.</p> <p>This design pattern proposes a solution to overcome this. The idea is to create a new table that stores the pre-computed state often using arrays or structs. Following the active users example, our table called <code class="language-plaintext highlighter-rouge">user_activity_cumulative</code> will have an array column that is updated daily by performing a <code class="language-plaintext highlighter-rouge">FULL OUTER JOIN</code> between today’s active users and yesterday’s cumulative table.</p> <table> <thead> <tr> <th><strong>user_id</strong></th> <th><strong>date</strong></th> <th><strong>activity_array_last_30_days</strong></th> </tr> </thead> <tbody> <tr> <td>123</td> <td>2025-11-12</td> <td><code class="language-plaintext highlighter-rouge">[1, 0, 1, 0, ..., 0]</code></td> </tr> <tr> <td>456</td> <td>2025-11-12</td> <td><code class="language-plaintext highlighter-rouge">[0, 1, 1, 0, ..., 0]</code></td> </tr> <tr> <td><strong>123</strong></td> <td><strong>2025-11-13</strong></td> <td><strong><code class="language-plaintext highlighter-rouge">[1, 1, 0, 1, ..., 0]</code></strong></td> </tr> <tr> <td><strong>456</strong></td> <td><strong>2025-11-13</strong></td> <td><strong><code class="language-plaintext highlighter-rouge">[0, 0, 1, 1, ..., 0]</code></strong></td> </tr> <tr> <td><strong>789</strong></td> <td><strong>2025-11-13</strong></td> <td><strong><code class="language-plaintext highlighter-rouge">[1, 0, 0, 0, ..., 0]</code></strong></td> </tr> </tbody> </table> <p style="text-align:center;">Table 1: Example of user_activity_cumulative Table</p> <p>Using this new table like shown above, finding out the 30-day active users for November 30th can be done by running the following query:</p> <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">      
   <span class="no">SELECT</span>
     <span class="no">COUNT</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
   <span class="no">FROM</span> <span class="n">user_activity_cumulative</span>
   <span class="no">WHERE</span>
     <span class="n">date</span> <span class="o">=</span> <span class="s1">'2025-11-13'</span>
     <span class="o">--</span> <span class="no">Sum</span> <span class="n">the</span> <span class="mi">30</span> <span class="n">elements</span> <span class="k">in</span> <span class="n">the</span> <span class="n">array</span><span class="p">.</span>
     <span class="nf">-</span><span class="o">-</span> <span class="no">If</span> <span class="n">the</span> <span class="n">sum</span> <span class="n">is</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">,</span> <span class="n">they</span> <span class="n">were</span> <span class="n">active</span> <span class="n">at</span> <span class="n">least</span> <span class="n">once</span><span class="o">.</span>
    <span class="no">AND</span> <span class="n">array_sum</span><span class="p">(</span><span class="n">activity_array_last_30_days</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span>
   
   </code></pre></figure> <p>For more details and concrete hands on example, you can check <a href="https://github.com/DataExpert-io/cumulative-table-design">this repository</a>.</p> <p>With this we have reached the end of this post, I hope you enjoyed it!</p> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <p><em>Stay tuned!</em></p> <h2 id="recap">Recap</h2> <p>In this article, we explored four essential data engineering design patterns that help build reliable and efficient data pipelines. The <strong>Write-Audit-Publish (WAP)</strong> pattern ensures data quality by validating data in a staging area before publishing to production and leveraged Apache Iceberg branches for an example implementation of this pattern. <strong>Change Data Capture (CDC)</strong> enables near real-time data synchronization by capturing incremental changes from source databases using log-based, trigger-based, or timestamp-based methods. <strong>Dead-Letter Queues (DLQ)</strong> provide a safety net for failed messages in streaming systems, improving observability and reducing data loss. Finally, the <strong>Cumulative Aggregate Table</strong> pattern optimizes performance for rolling window calculations by pre-computing and storing state, eliminating the need to scan large amounts of raw data repeatedly.</p> <p><em>Happy learning!</em></p> <h2 id="resources">Resources</h2> <p><a href="https://www.confluent.io/learn/change-data-capture/#why-change-data-capture">https://www.confluent.io/learn/change-data-capture/#why-change-data-capture</a></p> <p><a href="https://aws.amazon.com/blogs/big-data/build-write-audit-publish-pattern-with-apache-iceberg-branching-and-aws-glue-data-quality/">https://aws.amazon.com/blogs/big-data/build-write-audit-publish-pattern-with-apache-iceberg-branching-and-aws-glue-data-quality/</a></p> <p><a href="https://lakefs.io/blog/data-engineering-patterns-write-audit-publish/">https://lakefs.io/blog/data-engineering-patterns-write-audit-publish/</a></p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="Data Engineering"/><category term="Data Architecture"/><summary type="html"><![CDATA[Explore essential data engineering design patterns including WAP, CDC, DLQ and cumulative aggregate tables.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/articles/28_wap_iceberg_audit_branch.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/articles/28_wap_iceberg_audit_branch.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Serverless Architecture for Data Engineering</title><link href="https://www.firasesbai.com/articles/2025/10/24/serverless-architecture-for-data-engineering.html" rel="alternate" type="text/html" title="Serverless Architecture for Data Engineering"/><published>2025-10-24T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2025/10/24/serverless-architecture-for-data-engineering</id><content type="html" xml:base="https://www.firasesbai.com/articles/2025/10/24/serverless-architecture-for-data-engineering.html"><![CDATA[<p><em>Understanding serverless architecture in data engineering by example.</em></p> <p>Following the AWS Lambda launch in 2014 and the release of Amazon’s API gateway in 2015, “serverless” grew in popularity to become a new buzzword in the industry. Today, many organizations are considering serverless architectures as a way to save costs and operational overhead in building and running their applications.</p> <p>But what does it really mean to build with serverless, especially in data engineering? That is what we will try to cover in this article through concrete hands on example.</p> <p>All the source code is available <a href="https://github.com/firasesbai/data-engineering-examples/tree/main/nyc-taxi-etl-pipeline">here</a></p> <p><em>So let’s get started!</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#what-is-serverless" id="markdown-toc-what-is-serverless">What is Serverless?</a></li> <li><a href="#simplified-workflow-example" id="markdown-toc-simplified-workflow-example">Simplified Workflow Example</a></li> <li><a href="#serverless-and-software-design" id="markdown-toc-serverless-and-software-design">Serverless and Software Design</a></li> <li><a href="#challenges-and-limitations" id="markdown-toc-challenges-and-limitations">Challenges and Limitations</a></li> <li><a href="#how-to-choose" id="markdown-toc-how-to-choose">How to Choose?</a></li> <li><a href="#resources" id="markdown-toc-resources">Resources</a></li> </ul> <hr/> <h2 id="what-is-serverless">What is Serverless?</h2> <p><a href="https://aws.amazon.com/serverless/">According to AWS</a>, serverless computing is a set of “technologies for running code, managing data, and integrating applications, all without managing servers.”</p> <p>To elaborate, let’s first differentiate serverless offerings from fully-managed services where some server configuration is required. A good example would be <a href="https://aws.amazon.com/rds/aurora/">Amazon Aurora</a> which offers two modes:</p> <ul> <li><strong>Provisioned</strong>: a managed “instance-based” model where the instance size is configured by the developer and the cost model is usually a fixed hourly rate.</li> <li><strong>Serverless</strong>: no servers are configured by the developer. The database scales automatically and the cost model is pay-per-use.</li> </ul> <p>The core principles of a true serverless architecture are <strong>no server management</strong> and a <strong>pay-for-value</strong> cost model.</p> <h2 id="simplified-workflow-example">Simplified Workflow Example</h2> <p>The <a href="https://github.com/firasesbai/data-engineering-examples/tree/main/nyc-taxi-etl-pipeline">repository</a> contains an example of an ETL pipeline for processing NYC Taxi Trip data using AWS services. The goal is to ingest raw CSV data, transform it into a clean, query-ready format, and make it available for analysis. The entire architecture is built on a foundation of serverless AWS services.</p> <p>The entire infrastructure is defined as code using the <a href="https://docs.aws.amazon.com/cdk/v2/guide/home.html">AWS CDK</a> making the deployment of the whole stack straightforward with a single command.</p> <p>The solution implements the following architecture workflow:</p> <ul> <li><strong>Data Ingestion (Amazon S3):</strong> Raw CSV files land in an S3 bucket. S3 acts as our scalable, serverless data lake.</li> <li><strong>Orchestration (Amazon EventBridge &amp; AWS Lambda):</strong> An Amazon EventBridge rule, a serverless cron job, kicks off the pipeline on a schedule (e.g., daily at 4 AM UTC). The rule triggers a Lambda function which in return starts the main ETL job with the correct parameters.</li> <li><strong>ETL Processing (AWS Glue):</strong> The Lambda starts an AWS Glue job, which is the core of our pipeline. Glue is a serverless ETL service that automatically provisions compute resources to run our transformation script, and then shuts them down immediately after.</li> <li><strong>Data Cataloging (AWS Glue Crawler):</strong> After the job completes, a Glue Crawler inspects the processed data (now in optimized Parquet format), infers its schema, and updates the AWS Glue Data Catalog.</li> <li><strong>Querying (Amazon Athena):</strong> With the metadata available in the catalog, anyone can query the processed data in S3 using standard SQL with Athena, a serverless query engine.</li> </ul> <h2 id="serverless-and-software-design">Serverless and Software Design</h2> <p>Can you imagine a technology that lets you build automatically scaling, highly available software while optimizing for costs? This is the serverless proposition. But its real value is strategic, profoundly impacting how we approach software design. Specifically, how can you make any choices about your architecture, if your future use-cases aren’t known?</p> <p>When you don’t know the future, a <a href="https://martinfowler.com/bliki/SacrificialArchitecture.html">sacrificial architecture</a> is invaluable. Martin Fowler defines sacrificial architecture as an architecture designed to be thrown away if the concept proves successful. In our context, cloud environments and especially serverless offerings make sacrificial architecture more attractive in order to build a Minimum Viable Product (MVP). Our taxi pipeline is a perfect MVP. We can deploy it quickly, gather feedback, and if requirements change, we can discard it with minimal sunk cost.</p> <p>Now the success of a system depends on its ability to evolve. Designing a system isn’t a one-off operation. When the product direction, maturity, business requirements and team topologies change, then the success of a system will depend on its ability to evolve along with its demands. According to Martin Fowler’s foreword of the evolutionary architecture book: “The heart of doing evolutionary architecture is to make small changes, and put in feedback loops that allow everyone to learn from how the system is developing.”</p> <p>A conjecture that can help us think about this is the well-known <a href="https://martinfowler.com/bliki/DesignStaminaHypothesis.html">design stamina hypothesis</a>, which stipulates that a system that has not been designed may be easy to develop in the beginning, but after a certain point (the so-called “design pay-off line”), a well-designed system will outperform it.</p> <p>As shown in the diagram below, Serverless enables a “<strong>post-MVP redesign</strong>” strategy. You can start with a simple, serverless-first approach to ship quickly. As you learn and the system matures, you can refactor or redesign parts of the architecture without being locked into the initial design—a perfect example of an evolutionary architecture in practice.</p> <figure> <img src="/assets/images/articles/27_post_mvp_redesign.png" alt="post mvp redesign cumulative functionality"/> <figcaption>Figure 1: Post MVP Redesign Cumulative Functionality</figcaption> </figure> <p>The post-MVP redesign is comparably fast as the no-design approach delivering a lot of functionality and then shifts to the ideal scenario after redesigning to continuously deliver functionality in the long run.</p> <h2 id="challenges-and-limitations">Challenges and Limitations</h2> <p>That being said, it’s important to be aware of the challenges that come with serverless architectures.</p> <ul> <li> <p><strong>Cost Unpredictability:</strong> Unpredictable cost is a key aspect of serverless architecture that can be also a problem, especially for large enterprises operating on annually approved budgets. Cost unpredictability can make stakeholders reluctant to using a product where costs are mostly variable. For a scheduled workload like our ETL, the cost is quite predictable but that is not always the case and this is where active monitoring becomes a key to keep costs in check.</p> </li> <li> <p><strong>Platform Limitations:</strong> Serverless platforms are always evolving. In the past, the <a href="https://aws.amazon.com/glue/">AWS Glue</a> service for example had limitations in terms of runtime choice and memory customization. While many of these have been addressed, it’s a reminder that you are dependent on the provider’s roadmap.</p> </li> <li> <p><strong>Vendor Lock-in:</strong> Building on a specific cloud provider’s serverless offerings can tightly couple your application to their ecosystem, creating a trade-off between development speed and portability.</p> </li> </ul> <h2 id="how-to-choose">How to Choose?</h2> <p>So, should you go serverless? Consider these questions:</p> <ul> <li>Are you in a greenfield project where you need to iterate quickly and flexibly while building an MVP?</li> <li>Do you have a shortage of infrastructure developers in your team? Serverless empowers application developers to manage their own services.</li> </ul> <p>If you answered yes to any of these, a serverless architecture is a powerful option. It’s not just a technical choice but a strategic one that prioritizes speed, agility, and the ability to evolve with changing business requirements.</p> <p>With this we have reached the end of this post, I hope you enjoyed it!</p> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <p><em>Stay tuned!</em></p> <h2 id="resources">Resources</h2> <p><a href="https://www.datadoghq.com/state-of-serverless/">https://www.datadoghq.com/state-of-serverless/</a></p> <p><a href="https://martinfowler.com/articles/serverless.html">https://martinfowler.com/articles/serverless.html</a></p> <p><a href="https://www.thoughtworks.com/radar/techniques/serverless-architecture">https://www.thoughtworks.com/radar/techniques/serverless-architecture</a></p> <p><a href="https://www.thoughtworks.com/en-gb/radar/techniques/lambda-pinball">https://www.thoughtworks.com/en-gb/radar/techniques/lambda-pinball</a></p> <p><a href="https://www.freecodecamp.org/news/serverless-fully-managed-service-difference/">https://www.freecodecamp.org/news/serverless-fully-managed-service-difference/</a></p> <p><a href="https://blogs.perficient.com/2021/06/17/aws-cost-analysis-comparing-lambda-ec2-fargate/">https://blogs.perficient.com/2021/06/17/aws-cost-analysis-comparing-lambda-ec2-fargate/</a></p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="Data Engineering"/><category term="Data Architecture"/><category term="Cloud Computing"/><summary type="html"><![CDATA[understanding serverless architecture in data engineering by building etl pipeline for nyc taxi trip data.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/articles/27_post_mvp_redesign.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/articles/27_post_mvp_redesign.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">101 Apache Spark Cheatsheet</title><link href="https://www.firasesbai.com/articles/2025/10/10/apache-spark-101.html" rel="alternate" type="text/html" title="101 Apache Spark Cheatsheet"/><published>2025-10-10T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2025/10/10/apache-spark-101</id><content type="html" xml:base="https://www.firasesbai.com/articles/2025/10/10/apache-spark-101.html"><![CDATA[<p><em>101 Apache Spark Cheatsheet</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#what-is-apache-spark" id="markdown-toc-what-is-apache-spark">What is Apache Spark</a></li> <li><a href="#key-features-of-spark" id="markdown-toc-key-features-of-spark">Key features of Spark</a></li> <li><a href="#resilient-distributed-datasets" id="markdown-toc-resilient-distributed-datasets">Resilient Distributed Datasets</a> <ul> <li><a href="#transformations" id="markdown-toc-transformations">Transformations</a></li> <li><a href="#actions" id="markdown-toc-actions">Actions</a></li> <li><a href="#rdd-vs-dataframe" id="markdown-toc-rdd-vs-dataframe">RDD vs DataFrame</a></li> </ul> </li> <li><a href="#fault-tolerance" id="markdown-toc-fault-tolerance">Fault Tolerance</a></li> <li><a href="#spark-architecture" id="markdown-toc-spark-architecture">Spark Architecture</a></li> <li><a href="#spark-components" id="markdown-toc-spark-components">Spark Components</a></li> <li><a href="#lazy-evaluation" id="markdown-toc-lazy-evaluation">Lazy Evaluation</a></li> <li><a href="#dag-in-spark" id="markdown-toc-dag-in-spark">DAG in Spark</a></li> <li><a href="#caching" id="markdown-toc-caching">Caching</a></li> <li><a href="#broadcast-variables" id="markdown-toc-broadcast-variables">Broadcast Variables</a></li> <li><a href="#partitioning" id="markdown-toc-partitioning">Partitioning</a> <ul> <li><a href="#storage-partitioning-vs-spark-partitioning" id="markdown-toc-storage-partitioning-vs-spark-partitioning">Storage Partitioning vs Spark Partitioning</a></li> </ul> </li> <li><a href="#spark-applications-optimization" id="markdown-toc-spark-applications-optimization">Spark Applications Optimization</a></li> </ul> <hr/> <h2 id="what-is-apache-spark">What is Apache Spark</h2> <p>Apache Spark is an open-source, distributed computing system designed for fast and general-purpose data processing. It was developed to address the limitations of Hadoop MapReduce, offering significant performance improvements and a more flexible programming model.</p> <h2 id="key-features-of-spark">Key features of Spark</h2> <ul> <li><strong>Speed</strong>: Spark can be up to 100 times faster than Hadoop MapReduce for certain workloads, primarily due to its in-memory processing capabilities.</li> <li><strong>Ease of Use</strong>: Spark provides high-level APIs in Java, Scala, Python, and R, making it accessible to a wide range of developers and data scientists.</li> <li><strong>Unified Engine</strong>: Spark can handle diverse workloads including batch processing, interactive queries, streaming, machine learning, and graph processing, all within the same engine.</li> <li><strong>Fault Tolerance</strong>: Spark achieves fault tolerance through the use of Resilient Distributed Datasets (RDDs) and their lineage information.</li> <li><strong>Scalability</strong>: Spark can scale from one to thousands of nodes, allowing for efficient processing of large datasets.</li> <li><strong>Flexibility</strong>: Spark can run in various environments, including Hadoop, Mesos, standalone, or in the cloud and it can access diverse data sources.</li> </ul> <h2 id="resilient-distributed-datasets">Resilient Distributed Datasets</h2> <p>Resilient Distributed Datasets (RDD) is the fundamental data structure in Apache Spark.</p> <ul> <li><strong>Resilient</strong>: RDDs are fault-tolerant. If a partition of an RDD is lost due to node failure, it can be reconstructed using the lineage information.</li> <li><strong>Distributed</strong>: Data in RDDs is divided into partitions and distributed across nodes in a cluster.</li> <li><strong>Immutable</strong>: Once created, RDDs cannot be modified. Any transformation on an RDD creates a new RDD.</li> <li><strong>Lazy Evaluation</strong>: Transformations on RDDs are lazily evaluated. They are not computed until an action is called.</li> <li><strong>In-memory Computation</strong>: RDDs can be cached in memory for faster access in iterative algorithms.</li> </ul> <p>RDDs support two types of operations: <strong><em>Transformations</em></strong> and <strong><em>Actions</em></strong>.</p> <h3 id="transformations">Transformations</h3> <p>These are operations that create a new RDD from an existing one and they are lazy (not computed immediately).</p> <p>Examples of transformations:</p> <ul> <li><code class="language-plaintext highlighter-rouge">map(func)</code>: Apply a function to each element in the RDD. The result is an RDD with the same number of elements as the original.</li> <li><code class="language-plaintext highlighter-rouge">filter(func)</code>: Return a new RDD containing only the elements that pass the filter condition.</li> <li><code class="language-plaintext highlighter-rouge">flatMap(func)</code>: Similar to map, but each input item can be mapped to 0 or more output items. The results are flattened into a single RDD.</li> <li><code class="language-plaintext highlighter-rouge">groupByKey()</code>: Group the values for each key in the RDD.</li> <li><code class="language-plaintext highlighter-rouge">reduceByKey(func)</code>: Combine values with the same key using the provided function.</li> </ul> <p>Example of <code class="language-plaintext highlighter-rouge">map</code> vs <code class="language-plaintext highlighter-rouge">flatMap</code> usage:</p> <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">      
    <span class="n">rdd</span> <span class="o">=</span>  <span class="n">sc</span><span class="p">.</span><span class="nf">parallelize</span><span class="p">([</span><span class="s2">"Hello World"</span><span class="p">,</span> <span class="s2">"How are you"</span><span class="p">])</span>
    
    <span class="n">map_result</span> <span class="o">=</span> <span class="n">rdd</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nb">lambda</span> <span class="ss">x: </span><span class="n">x</span><span class="p">.</span><span class="nf">split</span><span class="p">())</span>
    <span class="c1"># Result: [["Hello", "World"], ["How", "are", "you"]]</span>

    <span class="n">flatmap_result</span> <span class="o">=</span> <span class="n">rdd</span><span class="p">.</span><span class="nf">flatMap</span><span class="p">(</span><span class="nb">lambda</span> <span class="ss">x: </span><span class="n">x</span><span class="p">.</span><span class="nf">split</span><span class="p">())</span>
    <span class="c1"># Result: ["Hello", "World", "How", "are", "you"]</span>
   
   </code></pre></figure> <h3 id="actions">Actions</h3> <p>These are operations that return a result to the driver program or write data to an external storage system. They trigger the execution of all the transformations that were called before it.</p> <p>Examples of actions:</p> <ul> <li><code class="language-plaintext highlighter-rouge">collect()</code>: Return all the elements of the RDD as an array to the driver program.</li> <li><code class="language-plaintext highlighter-rouge">count()</code>: Return the number of elements in the RDD.</li> <li><code class="language-plaintext highlighter-rouge">first()</code>: Return the first element of the RDD.</li> <li><code class="language-plaintext highlighter-rouge">take(n)</code>: Return an array with the first n elements of the RDD.</li> <li><code class="language-plaintext highlighter-rouge">reduce(func)</code>: Aggregate the elements of the RDD using a function.</li> <li><code class="language-plaintext highlighter-rouge">saveAsTextFile(path)</code>: Save the elements of the RDD as a text file.</li> </ul> <h3 id="rdd-vs-dataframe">RDD vs DataFrame</h3> <p>A Spark DataFrame is a distributed collection of data organized into named columns, similar to a table in a relational database or a data frame in R/Python. It’s built on top of RDDs and provides a more user-friendly API for structured and semi-structured data. It uses Catalyst Optimizer, which can significantly improve performance and has a defined Schema, allowing Spark to optimize query plans.</p> <h2 id="fault-tolerance">Fault Tolerance</h2> <p>Spark achieves fault tolerance primarily through the lineage of RDDs and the ability to recompute lost data.</p> <ul> <li><strong>RDD Lineage</strong>: <ul> <li>Each RDD maintains information about its lineage (how it was derived from other datasets).</li> <li>If a partition of an RDD is lost, Spark can rebuild it using this lineage information.</li> </ul> </li> <li><strong>Checkpointing</strong>: <ul> <li>For long lineage chains, Spark allows saving intermediate results to reliable storage (like HDFS).</li> <li>This reduces recovery time in case of failures.</li> </ul> </li> <li><strong>Speculative Execution</strong>: <ul> <li>Spark can run multiple copies of slower tasks to reduce the impact of stragglers.</li> </ul> </li> <li><strong>Stage Retry</strong>: <ul> <li>If a task fails, Spark will retry it on a different executor.</li> <li>If a whole stage fails, Spark can resubmit the entire stage.</li> </ul> </li> <li><strong>Data Replication</strong>: <ul> <li>When caching data, Spark can replicate it across nodes for added resilience.</li> </ul> </li> <li><strong>Driver and Worker Fault Tolerance</strong>: <ul> <li>Spark can recover from worker node failures.</li> <li>For driver failures, Spark supports checkpointing of the driver’s state in some deployment modes.</li> </ul> </li> </ul> <h2 id="spark-architecture">Spark Architecture</h2> <figure> <img src="/assets/images/articles/26_spark_architecture.png" alt="Spark Architecture"/> <figcaption>Figure 1: Spark Architecture - <a href="https://spark.apache.org/docs/latest/cluster-overview.html">Image Source</a></figcaption> </figure> <ul> <li><strong>Driver Program</strong>: Contains the main() function and creates a SparkContext.</li> <li><strong>SparkContext</strong>: The entry point for Spark functionality, representing the connection to the Spark cluster.</li> <li><strong>Cluster Manager</strong>: Allocates resources across applications.</li> <li><strong>Worker Nodes (Slaves)</strong>: <ul> <li>Execute the tasks assigned by the driver.</li> <li>Store data partitions.</li> </ul> </li> <li><strong>Executor</strong> <ul> <li>A process launched for an application on a worker node, that runs tasks and keeps data in memory or disk storage across them.</li> <li>Each application gets its own executor processes, which stay up for the duration of the whole application and run tasks in multiple threads. This has the benefit of isolating applications from each other, on both the scheduling side (each driver schedules its own tasks) and executor side (tasks from different applications run in different JVMs). However, it also means that data cannot be shared across different Spark applications (instances of SparkContext) without writing it to an external storage system.</li> </ul> </li> <li><strong>Task</strong>: A unit of work that will be sent to one executor.</li> <li><strong>Job</strong>: A parallel computation consisting of multiple tasks that gets spawned in response to a Spark action.</li> <li><strong>Stage</strong>: Each job gets divided into smaller sets of tasks called stages that depend on each other.</li> </ul> <h2 id="spark-components">Spark Components</h2> <ul> <li><strong>Spark Core</strong>: The foundation of the entire Spark system, providing distributed task dispatching, scheduling, and basic I/O functionalities.</li> <li><strong>Spark SQL</strong>: Module for working with structured data. It allows querying data via SQL as well as the Hive Query Language (HQL) and supports various sources like Hive tables, Parquet, and JSON.</li> <li><strong>Spark Streaming</strong>: Enables processing of live streams of data. It provides a high-level abstraction called DStream (discretized stream).</li> <li><strong>MLlib (Machine Learning Library)</strong>: A distributed machine learning framework on top of Spark Core. It provides various utilities for machine learning tasks, including classification, regression, clustering, and collaborative filtering.</li> <li><strong>GraphX</strong>: A distributed graph-processing framework on top of Spark. It provides an API for expressing graph computation and can model user-defined graphs.</li> <li><strong>SparkR</strong>: An R package that provides a light-weight frontend to use Spark from R.</li> </ul> <h2 id="lazy-evaluation">Lazy Evaluation</h2> <p>Lazy evaluation is a key optimization technique used in Spark. When you apply a transformation on an RDD, Spark doesn’t compute the results immediately. Instead, it remembers the set of transformations applied to some base dataset. The transformations are only computed when an action requires a result to be returned to the driver program.</p> <p>Importance of lazy evaluation:</p> <ul> <li><strong>Optimization</strong>: Spark can optimize the execution plan by analyzing the full set of transformations before executing.</li> <li><strong>Efficiency</strong>: It reduces the number of passes over the data by grouping operations.</li> <li><strong>Reduced Computation</strong>: If the final action only needs to compute a small result, Spark can minimize the amount of data processed.</li> <li><strong>Fault Tolerance</strong>: Lazy evaluation allows Spark to reconstruct lost data by recomputing only the lost partitions from the original data.</li> </ul> <h2 id="dag-in-spark">DAG in Spark</h2> <p>In Spark, a Directed Acyclic Graph (DAG) is a conceptual model of the execution plan for a set of operations on RDDs.</p> <p>A DAG is a graph where each node represents an RDD partition, and the edges represent the operations to be performed on the RDD. The edges have a direction, indicating the flow of data from one operation to the next. There are no cycles in the graph, meaning the operations flow in one direction and don’t loop back.</p> <p>Spark’s DAG scheduler optimizes the execution plan by analyzing the graph and combining operations where possible. The DAG is divided into stages. A stage is a set of tasks that can be executed together without shuffling data. If a node fails, Spark can reconstruct the lost partitions using the lineage information stored in the DAG.</p> <p>The DAG is constructed when actions are called, not when transformations are defined.</p> <h2 id="caching">Caching</h2> <p>Caching in Spark is a technique used to store the intermediate results of RDD computations in memory or disk. This allows faster access when the same RDD is used multiple times.</p> <ul> <li><strong>Methods</strong>: <ul> <li><code class="language-plaintext highlighter-rouge">cache()</code>: Stores the RDD in memory.</li> <li><code class="language-plaintext highlighter-rouge">persist()</code>: Allows specifying the storage level (memory, disk, or both).</li> </ul> </li> <li><strong>Storage Levels</strong>: Spark provides different storage levels like MEMORY_ONLY, MEMORY_AND_DISK, DISK_ONLY, etc.</li> <li><strong>Lazy Evaluation</strong>: Caching is also lazily evaluated. The RDD isn’t cached until the first action that uses it.</li> <li><strong>Automatic Memory Management</strong>: Spark automatically manages the cached data using LRU (Least Recently Used) eviction policy.</li> <li><strong>Unpersist</strong>: You can manually remove cached data using the unpersist() method.</li> </ul> <h2 id="broadcast-variables">Broadcast Variables</h2> <p>Broadcast variables in Spark are read-only variables that are cached on each machine in the cluster rather than shipped with every task. They are used to efficiently share large read-only data across all nodes in a cluster. They are commonly used for lookup tables, machine learning models, or any large read-only data structure.</p> <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">      
    <span class="n">lookup_table</span> <span class="o">=</span> <span class="n">sc</span><span class="p">.</span><span class="nf">broadcast</span><span class="p">({</span><span class="mi">1</span><span class="p">:</span> <span class="s2">"A"</span><span class="p">,</span> <span class="mi">2</span><span class="ss">:"B"</span><span class="p">,</span> <span class="mi">3</span><span class="ss">:"C"</span><span class="p">})</span>

    <span class="k">def</span> <span class="nf">lookup_function</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">lookup_table</span><span class="p">.</span><span class="nf">value</span><span class="p">.</span><span class="nf">get</span><span class="o">.</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="s2">"Unknown"</span><span class="p">)</span>
    
    <span class="n">result</span> <span class="o">=</span> <span class="n">rdd</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="n">lookup_function</span><span class="p">)</span>
   
   </code></pre></figure> <h2 id="partitioning">Partitioning</h2> <p>Spark automatically partitions RDDs based on the input data source or the number of cores available. There are three types of partitioning:</p> <ul> <li><em>Hash Partitioning</em>: Based on the hash code of the key.</li> <li><em>Range Partitioning</em>: Based on ordered keys.</li> <li><em>Custom Partitioning</em>: User-defined partitioning logic using methods like <code class="language-plaintext highlighter-rouge">repartition()</code> or <code class="language-plaintext highlighter-rouge">coalesce()</code>.</li> </ul> <p>Partitioning is important because:</p> <ul> <li>It affects the level of parallelism in data processing</li> <li>It influences the amount of data transfer during shuffles</li> <li>It impacts the efficiency of certain operations (e.g., joins, aggregations)</li> </ul> <h3 id="storage-partitioning-vs-spark-partitioning">Storage Partitioning vs Spark Partitioning</h3> <p>These are two separate concepts, but understanding the difference is relevant as storage partitioning can impact Spark’s performance.</p> <p>While storage partitioning refers to how data is organized on disk (e.g. in S3 or HDFS), Spark partitioning is about how data is distributed across executors for processing.</p> <p>Storage partitioning allows partition pruning; Spark can skip reading irrelevant partitions based on query filters which leads to faster query processing times by reducing data scanned.</p> <p>In order to read partitioned data, Spark performs partition discovery to identify partitions based on directory structure. It can also leverage partition information for optimizations like partition pruning. However, Spark still creates its own internal partitions for processing, which may differ from storage partitions.</p> <h2 id="spark-applications-optimization">Spark Applications Optimization</h2> <ul> <li><strong>Data Serialization</strong>: Use Kryo serialization instead of Java serialization for better performance.</li> <li><strong>Proper Data Partitioning</strong>: Ensure data is well-distributed across partitions to avoid skew.</li> <li><strong>Caching and Persistence</strong>: Use <code class="language-plaintext highlighter-rouge">cache()</code> or <code class="language-plaintext highlighter-rouge">persist()</code> for RDDs used multiple times.</li> <li><strong>Avoid Shuffling</strong>: Minimize operations that cause data shuffling (e.g., groupByKey, reduceByKey).</li> <li><strong>Use Broadcast Variables</strong>: For large shared data that needs to be distributed to all nodes.</li> <li><strong>Optimize Data Formats</strong>: Use columnar formats like Parquet for better compression and query performance.</li> <li><strong>Tune Spark Configurations</strong>: Adjust executor memory, number of executors, and other Spark parameters.</li> <li><strong>Use Appropriate Join Strategies</strong>: Choose the right join strategy (broadcast joins for small-large table joins).</li> <li><strong>Avoid UDFs When Possible</strong>: Use built-in functions instead of User Defined Functions for better performance.</li> <li><strong>Monitor and Profile</strong>: Use Spark UI and other profiling tools to identify bottlenecks.</li> </ul> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <p><em>Stay tuned!</em></p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="Data Engineering"/><summary type="html"><![CDATA[Apache Spark is an open-source, distributed computing system designed for fast and general-purpose data processing.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/articles/26_spark_architecture.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/articles/26_spark_architecture.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Learnings from Vibe Coding</title><link href="https://www.firasesbai.com/articles/2025/09/25/vibe-coding-learnings.html" rel="alternate" type="text/html" title="Learnings from Vibe Coding"/><published>2025-09-25T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2025/09/25/vibe-coding-learnings</id><content type="html" xml:base="https://www.firasesbai.com/articles/2025/09/25/vibe-coding-learnings.html"><![CDATA[<p><em>In this article, I share some lessons learned from trying out vibe coding.</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#what-is-vibe-coding" id="markdown-toc-what-is-vibe-coding">What is Vibe Coding?</a></li> <li><a href="#expectations" id="markdown-toc-expectations">Expectations</a></li> <li><a href="#key-takeaways" id="markdown-toc-key-takeaways">Key Takeaways</a></li> <li><a href="#recap" id="markdown-toc-recap">Recap</a></li> </ul> <hr/> <h2 id="what-is-vibe-coding">What is Vibe Coding?</h2> <p>Vibe coding is a term coined by Andrej Karpathy in his X post that was shared on February 2, 2025. However, the term has definitely evolved in my opinion since the original description. It is indeed a new kind of coding where the developer has become more like a moderator of multiple chat sessions and agents running in the background resulting in a handful of new and updated files with new generated code by the large language model that are waiting for his review.</p> <figure> <img src="/assets/images/articles/25_vibe_coding_andrej_karpathy_post.png" alt="Andrej Karpathy X Post"/> <figcaption>Figure 1: Andrej Karpathy's Post - <a href="https://x.com/karpathy/status/1886192184808149383?lang=en">Image Source</a></figcaption> </figure> <h2 id="expectations">Expectations</h2> <p>This is not gonna be one of those <em>I built a SaaS app in 15 minutes</em>. It is easy and at times surprisingly good how fast you can generate a landing page for an idea or have a decent CRUD app running but things get more complicated when building complex SaaS.</p> <figure> <img src="/assets/images/articles/25_vibe_coding_leo_post.png" alt="Leo's posts about Vibe Coding"/> <figcaption>Figure 2: Leo's posts about Vibe Coding - <a href="https://x.com/leojr94_?lang=en">Image Source</a></figcaption> </figure> <p>In my experiment, I wanted to answer a simple question: <em>can AI assisted development make me move faster through the backlog?</em></p> <p>Obviously faster does not always mean better and the metric for measuring the outcome should not be the number of lines of code generated because if anything you won’t be disappointed: LLMs can truly quickly generate a lot of code!</p> <p>As this was a personal side project, the stakes were not that high but the role of the moderator as mentioned earlier is still of huge importance. As you can see from the diagram below, I dealt with times where the model generated thousands of lines of code but almost always not all of it was accepted. You have to be a gatekeeper protecting your codebase from bugs or introducing weird behaviour and making sure to always follow clear design patterns and software development best practices.</p> <figure> <img src="/assets/images/articles/25_total_line_changes.png" alt="total line changes from chat"/> <figcaption>Figure 3: Total Line Changes from Chat</figcaption> </figure> <p>So over the course of two weeks, I used <strong>Cursor</strong> to try to answer my question. I did not start from scratch and vibe coded my way into this project but rather build on top of an existing web application and extended it with new features. That means I already had a clear code structure with clearly defined interfaces and domain models that have greatly influenced the LLM’s code organization and structure.</p> <p>When it comes to the features, some of them were straightforward but many weren’t. These were a collection of cards with merely a title in a Trello board that came from ideas I thought were cool to implement someday so I just wrote them down there quickly to not lose track of them. This point is important as we’ll see later because it influences how you approach building these features and how to draft your prompts for that.</p> <p>A final thing to mention before moving on to the key takeaways is the distribution of the used programming languages in the project:</p> <figure> <img src="/assets/images/articles/25_programming_language_usage.png" alt="programming language usage"/> <figcaption>Figure 4: Programming Language Usage</figcaption> </figure> <p>I’m not a frontend developer and my Javascript skills at this point were a bit rusty to say the least but surprisingly this is the part where I made most of the progress that I couldn’t have done without the AI assistance in such a short time.</p> <h2 id="key-takeaways">Key Takeaways</h2> <p>Following are the observations and notes I took as I progressed in this experiment:</p> <ul> <li>Use user journeys and expected behaviour in your prompt when explaining a feature especially one that would require changes across both frontend and backend logic.</li> <li>Be specific and start with thin slices. Describing multiple features and expectations in the same prompt just because they are correlated will not result in better results but rather only confuse the model.</li> <li>Request analysis of the code structure and design by giving the whole codebase as context to the mode. This helps you reflect on the progress made so far, assess the list of features implemented and decide what to do next because it is easy to have decision paralysis in this honeymoon phase where you are in love with how productive you are and everything seems feasible that you just want to do it all at once.</li> <li>Asking the model to simplify any implementation is always good: rely on your judgment of assessing what looks good and what looks meh.</li> <li>If the model gets stuck with a particular implementation or request, expect to have duplicated code snippets and functions with similar logic but slightly different names resulting from multiple attempts at solving the issue. This means that the model was really bad at cleaning up dead code unless you specifically ask it to evaluate some code by highlighting it.</li> <li>After several interactions within the same chat, it is better to continue or start over in a new one: It helps you clear your thoughts with a fresh new prompt and therefore guide the model to better output.</li> <li>I can’t stress this enough but if you are not starting from scratch and you will be updating an existing codebase, have your unit tests ready as your defense mechanism.</li> <li>From time to time, it is useful to start your prompt by specifically asking the model not to make code changes. Ask it to give you multiple implementation options and review the suggestions before proceeding with any of them. If needed drill down on the one you found most appealing with follow up questions and clarifications.</li> <li>Commit changes frequently especially between requests when you are satisfied with the suggested changes. This way it is easier to review follow up code changes in particular those made to the same files.</li> <li>Review the whole codebase once you have committed new changes to keep your understanding of it in check and be able to track down potential issues or improvements.</li> <li>Some changes are really small and you can just make them in place using the tab functionality:</li> </ul> <figure> <img src="/assets/images/articles/25_total_tabs_accepted.png" alt="total tabs accepted"/> <figcaption>Figure 5: Total Tabs Accepted</figcaption> </figure> <p>With this we have reached the end of this post, I hope you enjoyed it!</p> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <h2 id="recap">Recap</h2> <p>Coming back to the original quest of this whole experiment: did AI supported me in doing things faster? for sure it did. Like any other tool when used properly and consciously it will help augment your abilities to do your work better and support you in many ways. Will programmers be replaced by AI? I’ll take that with a pinch of salt.</p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="AI Engineering"/><summary type="html"><![CDATA[Lessons from trying out vibe coding using cursor]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/articles/25_total_line_changes.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/articles/25_total_line_changes.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">101 Apache Kafka Cheatsheet</title><link href="https://www.firasesbai.com/articles/2025/09/15/apache-kafka-101.html" rel="alternate" type="text/html" title="101 Apache Kafka Cheatsheet"/><published>2025-09-15T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2025/09/15/apache-kafka-101</id><content type="html" xml:base="https://www.firasesbai.com/articles/2025/09/15/apache-kafka-101.html"><![CDATA[<p><em>101 Apache Kafka Cheatsheet</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#what-is-apache-kafka" id="markdown-toc-what-is-apache-kafka">What is Apache Kafka?</a></li> <li><a href="#core-concepts" id="markdown-toc-core-concepts">Core Concepts</a></li> <li><a href="#key-features" id="markdown-toc-key-features">Key Features</a></li> <li><a href="#message-delivery-semantics" id="markdown-toc-message-delivery-semantics">Message delivery semantics</a></li> <li><a href="#use-cases" id="markdown-toc-use-cases">Use Cases</a></li> </ul> <hr/> <h2 id="what-is-apache-kafka">What is Apache Kafka?</h2> <p>Apache Kafka is an open-source distributed event streaming platform. It consists of highly scalable and fault tolerant <strong>servers</strong> enabling real-time data ingestion and processing between <strong>clients</strong> that are decoupled (source and target) and can scale independently.</p> <h2 id="core-concepts">Core Concepts</h2> <ul> <li><strong>Cluster</strong>: A group of Kafka broker servers that work together to manage and distribute data.</li> <li><strong>Broker</strong>: A Kafka server that stores data and serves client requests (producers and consumers). Multiple brokers form a cluster.</li> <li><strong>Topic</strong>: A named stream of records to which producers send data and from which consumers read. Topics are split into partitions.</li> <li><strong>Partition</strong>: Each topic is divided into partitions, which are ordered, immutable sequences of messages. Partitions enable parallelism and scalability.</li> <li><strong>Producer</strong>: An application or service that sends (publishes) records to Kafka topics.</li> <li><strong>Message Key</strong>: producers can choose to send a key with records then messages for that key will always go to the same partition.</li> <li><strong>Consumer</strong>: An application or service that reads (subscribes to) records from Kafka topics.</li> <li><strong>Consumer Group</strong>: A group of consumers that work together to consume data from a topic, ensuring each message is processed only once by the group.</li> <li><strong>ZooKeeper</strong>: Used for managing and coordinating Kafka brokers (leader election, metadata, etc.). Note: Newer Kafka versions are moving toward removing ZooKeeper dependency.</li> <li><strong>Offset</strong>: A unique identifier for each message within a partition, used by consumers to keep track of read messages.</li> <li><strong>Replication</strong>: Each partition can be replicated across multiple brokers to ensure durability and high availability.</li> </ul> <figure> <img src="/assets/images/articles/24_apache_kafka_architecture.png" alt="Example Kafka Topic &amp; Producers "/> <figcaption>Figure 1: Example Kafka Topic &amp; Producers - <a href="https://kafka.apache.org/documentation/#gettingStarted">Image Source</a></figcaption> </figure> <h2 id="key-features">Key Features</h2> <ul> <li><strong>High Throughput</strong>: Capable of handling millions of messages per second.</li> <li><strong>Low Latency</strong>: Designed for real-time streaming and processing.</li> <li><strong>Scalability</strong>: Scales horizontally by adding brokers and partitions.</li> <li><strong>Fault Tolerance</strong>: Data is replicated across brokers; if one fails, another can take over.</li> <li><strong>Durability</strong>: Messages are persisted on disk and replicated.</li> <li><strong>Decoupling</strong>: Producers and consumers are independent, enabling flexible architectures.</li> <li><strong>Multiple APIs</strong>: <ul> <li><em>Admin API</em>: manage and inspect topics, brokers, and other Kafka objects.</li> <li><em>Producer API</em>: Publish data to topics.</li> <li><em>Consumer API</em>: Subscribe to and process data from topics.</li> <li><em>Streams API</em>: Build stream processing applications.</li> <li><em>Connect API</em>: Integrate with external systems (databases, file systems, etc.)</li> </ul> </li> </ul> <h2 id="message-delivery-semantics">Message delivery semantics</h2> <p>Apache Kafka provides three primary message delivery semantics:</p> <ul> <li><strong>At-most-once</strong>: Messages are delivered zero or one time. Some messages may be lost, but never delivered more than once. Typical usage in applicaitons with high-throughput, low latency requirements and risk of data loss.</li> <li><strong>At-least-once</strong>: Messages are delivered one or more times. No message is lost, but duplicates may occur. Typical usage in data pipelines where no data loss is acceptable, and duplicates can be handled.</li> <li><strong>Exactly-once</strong>: Each message is delivered once and only once. No data loss or duplicate delivery. Ensures no message loss or duplication, but with increased latency and configuration overhead Typical usage in Financial transactions and critical data flows.</li> </ul> <p>How kafka achieves these semantics:</p> <ul> <li>At-most-once: <ul> <li>Producer sends messages without waiting for acknowledgment (acks=0).</li> <li>If a failure occurs before delivery, messages may be lost.</li> <li>Consumer commits its offset before processing messages. If it crashes after committing but before processing, messages are lost.</li> </ul> </li> <li>At-least-once: <ul> <li>Producer waits for acknowledgment (acks=1 or acks=all).</li> <li>If acknowledgment is not received, the producer retries, which can result in duplicate messages.</li> <li>Consumers must be idempotent to handle possible duplicates</li> <li>Consumer commits offset after processing. If it crashes before committing, messages may be processed again after recovery (duplicates possible).</li> </ul> </li> <li>Exactly-once: <ul> <li>Producer uses idempotence and transactions; each message is written once even if retried.</li> <li>Consumers and producers must be properly configured for transactional processing. Offset commits and output are part of the same transaction, ensuring atomicity.</li> </ul> </li> </ul> <h2 id="use-cases">Use Cases</h2> <p>Some of the popular use cases for Apache Kafka include:</p> <ul> <li><strong>Messaging</strong>: replacement to traditional message broker for decoupling data processing between producers and consumers.</li> <li><strong>Website activity tracking</strong>: this is the original use case where a user’s site activity like page views, clicks and searches events are published to central topics and available for consumption from real time analytics and insights applications.</li> <li><strong>Metrics</strong>: similarly apache kafka is used in aggregating statistics from distributed applications to produce centralized feeds of operational data.</li> <li><strong>Log aggregation</strong>: used as a replacement for log aggregation solutions giving cleaner abstraction of log or event data as a stream of messages.</li> </ul> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <p><em>Stay tuned!</em></p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="Data Engineering"/><summary type="html"><![CDATA[Apache Kafka is a highly scaled, distributed and fault tolerant event streaming platform.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/articles/24_apache_kafka_architecture.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/articles/24_apache_kafka_architecture.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">101 Apache Airflow Cheatsheet</title><link href="https://www.firasesbai.com/articles/2025/09/08/apache-airflow-101.html" rel="alternate" type="text/html" title="101 Apache Airflow Cheatsheet"/><published>2025-09-08T00:00:00+00:00</published><updated>2026-05-07T07:43:45+00:00</updated><id>https://www.firasesbai.com/articles/2025/09/08/apache-airflow-101</id><content type="html" xml:base="https://www.firasesbai.com/articles/2025/09/08/apache-airflow-101.html"><![CDATA[<p><em>101 Apache Airflow Cheatsheet.</em></p> <hr/> <h1> Contents </h1> <ul id="markdown-toc"> <li><a href="#what-is-apache-airflow" id="markdown-toc-what-is-apache-airflow">What is Apache Airflow?</a></li> <li><a href="#core-concepts" id="markdown-toc-core-concepts">Core Concepts</a> <ul> <li><a href="#directed-acyclic-graphs-dags" id="markdown-toc-directed-acyclic-graphs-dags">Directed Acyclic Graphs (DAGs)</a></li> <li><a href="#dag-run" id="markdown-toc-dag-run">DAG Run</a></li> <li><a href="#tasks" id="markdown-toc-tasks">Tasks</a></li> <li><a href="#task-instances" id="markdown-toc-task-instances">Task Instances</a></li> <li><a href="#variables" id="markdown-toc-variables">Variables</a></li> </ul> </li> <li><a href="#architecture-components" id="markdown-toc-architecture-components">Architecture Components</a></li> <li><a href="#architecture-components-1" id="markdown-toc-architecture-components-1">Architecture Components</a></li> </ul> <hr/> <h2 id="what-is-apache-airflow">What is Apache Airflow?</h2> <p>Apache Airflow is an open-source workflow management platform designed to programmatically author, schedule, and monitor complex data pipelines.</p> <h2 id="core-concepts">Core Concepts</h2> <h3 id="directed-acyclic-graphs-dags">Directed Acyclic Graphs (DAGs)</h3> <p>The fundamental structure in Airflow, representing a collection of tasks with defined dependencies. Each DAG is defined in Python code and dictates the order of task execution based on their relationships. A DAG is a graph structure where tasks are represented as nodes, and the dependencies between these tasks are represented as directed edges. The “directed” aspect indicates that tasks have a specific order of execution, while “acyclic” means there are no loops or cycles, preventing infinite execution paths.</p> <h3 id="dag-run">DAG Run</h3> <p>A DAG Run is an object representing an instantiation of the DAG in time. Any time the DAG is executed, a DAG Run is created and all tasks inside it are executed. The status of the DAG Run depends on the tasks states. Each DAG Run is run separately from one another, meaning that you can have many runs of a DAG at the same time.</p> <h3 id="tasks">Tasks</h3> <p>Task is the individual units of work within a DAG. Each task represents a single operation, such as data extraction, transformation, or loading (ETL). The relationships between tasks are established using dependency definitions. This can be done through:</p> <ul> <li>Bitwise Operators: Using » to set downstream dependencies and « for upstream dependencies.</li> <li>Methods: Using set_upstream() and set_downstream() methods to explicitly define task relationships.</li> </ul> <p>There are three common types of task:</p> <ul> <li><strong>Operators</strong>, conceptually a template for predefined tasks that you can string together quickly to build most parts of your DAGs.</li> <li><strong>Sensors</strong>, a special subclass of Operators which are entirely about waiting for an external event to happen.</li> <li>A <strong>TaskFlow-decorated</strong> @task, which is a custom Python function packaged up as a Task.</li> </ul> <p>To pass data between tasks you have three options:</p> <ul> <li><strong>XComs</strong> (“Cross-communications”), a system where you can have tasks push and pull small bits of metadata identified by a <strong>key</strong> as well as the <strong>task_id</strong> and <strong>dag_id</strong> it came from.</li> <li>Uploading and downloading large files from a storage service (either one you run, or part of a public cloud)</li> <li><strong>TaskFlow API</strong> automatically passes data between tasks via implicit XComs</li> </ul> <h3 id="task-instances">Task Instances</h3> <p>Much in the same way that a DAG is instantiated into a DAG Run each time it runs, task instances are specific executions of tasks at particular times, which can vary based on the DAG’s scheduling.</p> <h3 id="variables">Variables</h3> <p>Variables are Airflow’s runtime configuration concept - a general key/value store that is global and can be queried from your tasks, and easily set via Airflow’s user interface, or bulk-uploaded as a JSON file. Variables are <strong>global</strong>, and should only be used for overall configuration that covers the entire installation; to pass data from one Task/Operator to another, you should use XComs instead.</p> <h2 id="architecture-components">Architecture Components</h2> <figure> <img src="/assets/images/articles/23_apache_airflow_architecture.png" alt="Apache Airflow Architecture Components"/> <figcaption>Figure 1: Apache Airflow Architecture Components - <a href="https://airflow.apache.org/docs/apache-airflow/2.1.2/concepts/overview.html">Image Source</a></figcaption> </figure> <ul> <li><strong>Scheduler</strong>: The component responsible for scheduling tasks and determining when they should run. It checks the DAG directory for tasks that need to be executed.</li> <li><strong>Executor</strong>: This defines how and where tasks are executed. Various executors are available. In the default Airflow installation, this runs everything inside the scheduler, but most production-suitable executors actually push task execution out to workers. Most executors will generally also introduce other components to let them talk to their workers - like a <strong>task queue</strong> - but you can still think of the executor and its workers as a single logical component</li> <li><strong>Web Server</strong>: Provides a user interface for monitoring and managing workflows, allowing users to inspect DAGs and task statuses.</li> <li><strong>A folder of DAG files</strong>, read by the scheduler and executor (and any workers the executor has)</li> <li><strong>Metadata Database</strong>: Stores all metadata related to DAGs and tasks, typically using PostgreSQL or MySQL.</li> </ul> <h2 id="architecture-components-1">Architecture Components</h2> <ul> <li><strong>Task Management</strong>: Airflow manages task dependencies automatically, ensuring that tasks execute in the correct order.</li> <li><strong>Scheduling</strong>: Airflow provides advanced scheduling capabilities, allowing workflows to run on defined schedules or trigger based on external events.</li> <li><strong>Extensibility</strong>: Users can create custom operators and plugins to extend Airflow’s functionality, integrating with various data sources and services.</li> <li><strong>Error Handling and Retries</strong>: Built-in mechanisms allow tasks to be retried automatically upon failure, enhancing workflow reliability.</li> <li><strong>Scalability</strong>: Airflow can handle thousands of concurrent tasks across multiple workers, making it suitable for large-scale data operations.</li> <li><strong>Rich Command Line Interface (CLI)</strong>: The CLI provides utilities for managing DAGs and executing tasks directly from the command line.</li> <li><strong>Integration with Other Tools</strong>: Airflow supports integration with various cloud services and data tools, including AWS, Google Cloud Platform, and many others.</li> </ul> <p>If you have any remarks or questions, please don’t hesitate and do drop a comment below.</p> <p><em>Happy learning!</em></p>]]></content><author><name>Firas Esbai</name></author><category term="articles"/><category term="Data Engineering"/><summary type="html"><![CDATA[Apache Airflow simplifies workflow automation with DAGs, task scheduling, and data pipeline management for data engineering workflows.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.firasesbai.com/assets/images/articles/23_apache_airflow_architecture.png"/><media:content medium="image" url="https://www.firasesbai.com/assets/images/articles/23_apache_airflow_architecture.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry></feed>