<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Geeky Tales]]></title><description><![CDATA[Exploring the evolution from Business Intelligence to Artificial Intelligence.
Insights, frameworks, and stories on building intelligent, data-driven enterprises.]]></description><link>https://ganeshkedari.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!M1sj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4725bbf2-2e7d-4bd8-addc-8949cf725dd8_500x500.png</url><title>The Geeky Tales</title><link>https://ganeshkedari.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 21 Jul 2026 16:08:15 GMT</lastBuildDate><atom:link href="https://ganeshkedari.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Ganesh Sopan Kedari]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[ganeshkedari@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[ganeshkedari@substack.com]]></itunes:email><itunes:name><![CDATA[Ganesh Kedari]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ganesh Kedari]]></itunes:author><googleplay:owner><![CDATA[ganeshkedari@substack.com]]></googleplay:owner><googleplay:email><![CDATA[ganeshkedari@substack.com]]></googleplay:email><googleplay:author><![CDATA[Ganesh Kedari]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Building Production-Class Data APIs on Snowflake and Lambda]]></title><description><![CDATA[A practical guide to HTTP method design, filter architecture, RBAC propagation, async query handling, and frontend integration for data engineers building production APIs on Snowflake.]]></description><link>https://ganeshkedari.substack.com/p/building-production-class-data-apis</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/building-production-class-data-apis</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Thu, 28 May 2026 17:05:51 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b7d72de2-508c-4706-84b0-9a610032aa69_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The first version always works. That&#8217;s what makes it dangerous.</p><p>A Lambda function, a Snowflake connector, a SQL query, a JSON response. You wire it up in an afternoon, the product team runs some test queries, everyone is happy. Then real usage arrives. Date ranges that were fine in testing now return 20,000 rows and the connection times out. A filter endpoint built on GET query parameters breaks when the product team asks for multi-value status filters and a list of 40 specific order IDs. A polling client retries every second under failure conditions, burning warehouse capacity and holding up every other consumer of the API. And nobody thought about what happens when a user from one business unit queries data that belongs to another.</p><p>None of those failures are surprising in retrospect. They&#8217;re the default outcome when design decisions get deferred to production.</p><p>This is not a tutorial on connecting Lambda to Snowflake. That part is straightforward. This is about the decisions that separate an API that works from one that holds up the method contract, the filter architecture, the pagination model, the access control layer, and how the whole stack connects back to a frontend UI.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rwLo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rwLo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!rwLo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!rwLo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!rwLo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rwLo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1039681,&quot;alt&quot;:&quot;Architecture diagram of API Gateway to Lambda to Snowflake flow with timeout and concurrency constraints labeled at each seam&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/199571661?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Architecture diagram of API Gateway to Lambda to Snowflake flow with timeout and concurrency constraints labeled at each seam" title="Architecture diagram of API Gateway to Lambda to Snowflake flow with timeout and concurrency constraints labeled at each seam" srcset="https://substackcdn.com/image/fetch/$s_!rwLo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!rwLo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!rwLo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!rwLo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfb1a537-291a-42a5-a15b-adf8a7373063_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Why the First Version Breaks</h3><p>The architecture that powers most first-version data APIs looks like this: </p><p>API Gateway receives the request &#8594; invokes a Lambda function &#8594; the Lambda function executes a Snowflake query &#8594; Serializes the result &#8594; returns it. <br>Four components. One synchronous path.</p><p>The seams in that path have hard limits that don&#8217;t show up in development.</p><p>API Gateway&#8217;s integration timeout is 29 seconds for REST and HTTP API integrations. That limit is not configurable. If Lambda doesn&#8217;t return within 29 seconds, the client gets a 504. The Snowflake query may still be running.</p><p>Lambda cold starts add latency on top. A Python Lambda with the Snowflake connector initializes in the range of 1&#8211;3 seconds in non-VPC deployments. For Lambdas attached to a VPC  which is common when Snowflake is accessed via a private endpoint  cold start latency is higher. That initialization time comes directly out of the 29-second budget. Provisioned concurrency eliminates cold starts at the cost of reserved capacity.</p><p>Snowflake&#8217;s warehouse queues concurrent queries when demand exceeds current capacity. For single-cluster warehouses, additional queries wait in line rather than fail immediately. Under API load, response time is not just a function of query complexity  it&#8217;s a function of how many other queries are competing for warehouse resources at the same moment. Multi-cluster warehouses with auto-scaling handle this by adding clusters, but each cluster adds to credit consumption.</p><p>These are fixed constraints. The design adapts to them. Ignoring them in the initial build just means discovering them under conditions that matter.</p><div><hr></div><h3>HTTP Methods for Data APIs: Which One and When</h3><p>HTTP verbs are not just a naming convention  they carry semantics that affect caching, client behavior, API gateway configuration, and what intermediaries are allowed to do with the request. For data APIs, choosing the wrong verb causes problems that range from missed cache hits to security exposures.</p><p>Here is how the common methods map to data API use cases, with the trade-offs for each.</p><h4>GET - Resource Lookup</h4><p>GET is safe (no side effects) and idempotent (same request produces the same result). HTTP caches at every layer - browser, CDN, API Gateway - treat GET as cacheable by default. That&#8217;s the main reason to use it.</p><p>Use GET for resource lookups where the parameters are simple and enumerable: a specific order by ID, a product by SKU, a reference table entry by code. The URL is the cache key. Two clients requesting the same URL get the same cached response from API Gateway without invoking Lambda or hitting Snowflake.</p><p><strong>Pros:</strong> API Gateway and CDN caching work out of the box. URLs are bookmarkable and loggable. Semantics are unambiguous - clients and intermediaries know it has no side effects.</p><p><strong>Cons:</strong> URL length limits in practice (around 2,000 characters in most environments). Query parameters are exposed in access logs, server logs, and browser history don&#8217;t put sensitive filter values in GET parameters. Complex, multi-dimensional filter predicates don&#8217;t encode cleanly into URL parameters.</p><h4><strong>POST - Analytical Queries and Resource Creation</strong></h4><p>POST has no caching semantics by default and is not idempotent by RFC definition. For data APIs, it&#8217;s the right choice for two distinct use cases: </p><ol><li><p>Creating a resource (a job submission, a bookmark) where the side effect is intentional </p></li><li><p>Executing a parameterized analytical query where the filter payload is complex.</p></li></ol><p>The case for POST on analytical endpoints: the request body carries a structured JSON filter contract without URL constraints. Multiple filter dimensions compose cleanly. The contract is explicit and can be versioned. Filtering on 40 customer IDs, three status codes, a date range, and a region group is a normal POST body it would be a broken URL.</p><p><strong>Pros:</strong> No payload size constraint. Structured JSON body is explicit, version-able, and doesn&#8217;t expose filter values in logs. Clean contract for complex filters.</p><p><strong>Cons:</strong> Not cached by HTTP intermediaries by default. Semantic mismatch POST implies a side effect, but read-only queries have none. Clients and proxies may not treat repeat POST requests the same way they treat repeat GETs.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TLk2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TLk2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!TLk2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!TLk2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!TLk2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TLk2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1092958,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/199571661?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TLk2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!TLk2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!TLk2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!TLk2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4a605c8-dea7-44c8-8214-1c8ddbe28af8_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h4>PUT and PATCH - Updates</h4><p>PUT replaces a resource entirely. PATCH applies a partial update. Neither is typically the right choice for data retrieval endpoints. They appear in data APIs when the product team needs to update configuration, preferences, or metadata through the same API surface.</p><p><strong>PUT:</strong> Idempotent. Safe to retry repeating the request produces the same result. Use it when the client is sending the full representation of the resource.</p><p><strong>PATCH:</strong> Partial update. Not idempotent by default unless the operation is designed to be (a `$set` operation is idempotent; an `$increment` operation is not). Use when the client is updating specific fields without replacing the whole resource.</p><p>Neither PUT nor PATCH is relevant to Snowflake query APIs specifically. They belong in the API surface only when there&#8217;s actual write behavior.</p><div><hr></div><h4><strong>DELETE </strong></h4><p>Idempotent. Use for deleting a specific resource. In data APIs, this appears when clients can delete saved queries, bookmarks, or async job results. A `DELETE /reports/jobs/{job_id}` to cancel a running async job or clean up results from S3 is a reasonable use.</p><div><hr></div><h3>The HTTP QUERY Method - Emerging Standard Worth Knowing</h3><p>The IETF HTTPBIS working group has proposed a new HTTP method called QUERY. It&#8217;s designed to solve the exact problem that makes data API verb selection uncomfortable: a safe, idempotent request that can carry a structured request body.</p><p>QUERY combines GET&#8217;s safety semantics (no side effects, idempotent) with POST&#8217;s ability to carry a request body. Caches can treat a QUERY response as cacheable - unlike POST - because the method is explicitly defined as safe. This is the semantic gap that forces teams to either use GET with constrained parameters or POST with no cacheability.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;1f7821b6-5c16-4034-95ed-7a37fbcb7a79&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">QUERY /orders HTTP/1.1
Content-Type: application/json
{
    &#8220;filters&#8221;: {
        &#8220;status&#8221;: [&#8221;pending&#8221;, &#8220;processing&#8221;],
        &#8220;order_date&#8221;: {&#8221;gte&#8221;: &#8220;2024-01-01&#8221;},
        &#8220;customer_ids&#8221;: [1042, 5831, 9204]
    },
    &#8220;page_size&#8221;: 100
}</code></pre></div><p>The response can be cached by intermediaries that understand the QUERY method, keyed on both the URL and the request body.</p><p><strong>Pros:</strong> Correct semantics - safe, idempotent, body-bearing. Cacheability is defined in the spec. Resolves the GET vs POST tension for search and query operations.</p><p><strong>Cons:</strong> As of mid-2026, QUERY remains an IETF draft. API Gateway does not support it as a first-class method. Most HTTP clients require explicit configuration. CDNs and proxies don&#8217;t cache QUERY responses yet. Adopting QUERY today means carrying the implementation complexity without the infrastructure benefits.</p><p>The practical recommendation: design your filter payloads using POST for now with a clean JSON body contract. Monitor the QUERY spec progression. When major API gateways and CDNs add support, migrating well-structured POST endpoints to QUERY will be straightforward.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!B-yZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!B-yZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!B-yZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!B-yZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!B-yZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!B-yZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1208982,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/199571661?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!B-yZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!B-yZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!B-yZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!B-yZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9fe198d-e80d-40c6-adda-bb25c4adc0db_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h3>Handling Large Filter Sets Without Breaking the API</h3><p>The filter problem compounds over time. An API ships with three filter fields. The product team adds requirements. Six months later, the endpoint has 15 possible dimensions - date ranges, status codes, region groups, customer tiers, assigned teams, custom tags. A year later, some endpoints have 40.</p><p>At that scale, ad-hoc filter design breaks. Query parameters can&#8217;t carry the payload. String-concatenated SQL is a query injection waiting to happen. A flat JSON object with 40 keys becomes impossible to document, validate, or evolve.</p><p>The right approach is to define a structured filter DSL - a small, typed query language expressed as JSON that the API validates before translating to SQL.</p><h4>Design a Typed Filter Schema</h4><p>A filter DSL has three components: a field name, an operator, and a value. Logical combinators (AND, OR) wrap multiple conditions.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;22c7b657-841b-42c0-a1ec-b7f373c70602&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Structured filter payload - sent by the client
{
    &#8220;filters&#8221;: {
        &#8220;and&#8221;: [
            {&#8221;field&#8221;: &#8220;status&#8221;, &#8220;op&#8221;: &#8220;in&#8221;, &#8220;value&#8221;: [&#8221;pending&#8221;, &#8220;processing&#8221;]},
            {&#8221;field&#8221;: &#8220;order_date&#8221;, &#8220;op&#8221;: &#8220;gte&#8221;, &#8220;value&#8221;: &#8220;2024-01-01&#8221;},
            {&#8221;field&#8221;: &#8220;order_date&#8221;, &#8220;op&#8221;: &#8220;lt&#8221;, &#8220;value&#8221;: &#8220;2024-04-01&#8221;},
            {&#8221;field&#8221;: &#8220;customer_id&#8221;, &#8220;op&#8221;: &#8220;in&#8221;, &#8220;value&#8221;: [1042, 5831, 9204]},
            {&#8221;field&#8221;: &#8220;region&#8221;, &#8220;op&#8221;: &#8220;eq&#8221;, &#8220;value&#8221;: &#8220;EMEA&#8221;}
        ]
    },
    &#8220;sort&#8221;: {&#8221;field&#8221;: &#8220;order_date&#8221;, &#8220;direction&#8221;: &#8220;desc&#8221;},
    &#8220;page_size&#8221;: 100
}</code></pre></div><p>The API validates the filter against a schema before touching SQL. Every field name is validated against an allowlist. Every operator is validated against the field&#8217;s permitted operators. The value type is validated against the field&#8217;s data type. Nothing from the client reaches Snowflake without passing that validation.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;4017e3dd-9c22-49a6-af61-cae9b6900de5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Server-side: allowlisted field definitions
FILTER_FIELDS = {
    &#8220;status&#8221;: {
        &#8220;type&#8221;: &#8220;string&#8221;,
        &#8220;allowed_ops&#8221;: [&#8221;eq&#8221;, &#8220;in&#8221;, &#8220;not_in&#8221;],
        &#8220;snowflake_column&#8221;: &#8220;order_status&#8221;
    },
    &#8220;order_date&#8221;: {
        &#8220;type&#8221;: &#8220;date&#8221;,
        &#8220;allowed_ops&#8221;: [&#8221;eq&#8221;, &#8220;gte&#8221;, &#8220;lte&#8221;, &#8220;lt&#8221;, &#8220;gt&#8221;],
        &#8220;snowflake_column&#8221;: &#8220;created_date&#8221;
    },
    &#8220;customer_id&#8221;: {
        &#8220;type&#8221;: &#8220;integer&#8221;,
        &#8220;allowed_ops&#8221;: [&#8221;eq&#8221;, &#8220;in&#8221;],
        &#8220;snowflake_column&#8221;: &#8220;customer_id&#8221;
    },
    &#8220;region&#8221;: {
        &#8220;type&#8221;: &#8220;string&#8221;,
        &#8220;allowed_ops&#8221;: [&#8221;eq&#8221;, &#8220;in&#8221;],
        &#8220;snowflake_column&#8221;: &#8220;region_code&#8221;
    }
}

def build_where_clause(filters: list, params: list) -&gt; str:
    clauses = []
    for f in filters:
        field_def = FILTER_FIELDS.get(f[&#8221;field&#8221;])
        if not field_def:
            raise ValueError(f&#8221;Unknown filter field: {f[&#8217;field&#8217;]}&#8221;)
        if f[&#8221;op&#8221;] not in field_def[&#8221;allowed_ops&#8221;]:
            raise ValueError(f&#8221;Operator {f[&#8217;op&#8217;]} not allowed on {f[&#8217;field&#8217;]}&#8221;)
        col = field_def[&#8221;snowflake_column&#8221;]
        if f[&#8221;op&#8221;] == &#8220;in&#8221;:
            placeholders = &#8220;, &#8220;.join([&#8221;%s&#8221;] * len(f[&#8221;value&#8221;]))
            clauses.append(f&#8221;{col} IN ({placeholders})&#8221;)
            params.extend(f[&#8221;value&#8221;])
        elif f[&#8221;op&#8221;] == &#8220;gte&#8221;:
            clauses.append(f&#8221;{col} &gt;= %s&#8221;)
            params.append(f[&#8221;value&#8221;])
        # ... other operators

    return &#8220; AND &#8220;.join(clauses)</code></pre></div><p>This approach keeps SQL construction entirely server-side, uses parameterized queries throughout, and makes the validation logic the single place where new filter fields are registered. Adding a new filterable field means adding one entry to `FILTER_FIELDS`. Adding a new operator means adding one branch to `build_where_clause`.</p><h4>Handle Large IN Lists Carefully</h4><p>Filters with large IN lists - <code>customer_id IN (list of 500 IDs)</code> - deserve specific attention. Very large IN lists can prevent Snowflake from using pruning and partition elimination effectively. There&#8217;s no hard cutoff, but IN lists over a few hundred values are worth testing explicitly.</p><p>For large IN list cases, consider an alternative: the client POSTs the large ID list to a staging endpoint that loads it into a temporary table, then references the temp table in the query. Snowflake can join against a temp table more efficiently than evaluating a large IN predicate in some cases.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;e705bcc0-c127-4a56-8b34-dff3f6d6fa42&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql"># Alternative for very large IN lists: temp table join
&#8220;&#8221;&#8220;
CREATE TEMPORARY TABLE temp_customer_ids (id INTEGER);
INSERT INTO temp_customer_ids VALUES (%s), ...;
SELECT o.*
FROM orders o
JOIN temp_customer_ids t ON o.customer_id = t.id
WHERE [other filters]
ORDER BY o.order_date DESC
LIMIT %s
&#8220;&#8221;&#8220;</code></pre></div><div class="pullquote"><p>Profile before optimizing, Snowflake&#8217;s IN list handling is often efficient at moderate sizes, and the temp table approach adds roundtrip overhead.</p></div><h4>Prevent Expensive Filter Combinations</h4><p>An open filter schema allows clients to construct queries that are cheap in dev (small data, short date ranges) and catastrophic in production (broad date ranges, no high-cardinality filters, cross-joins through the filter structure). Three mitigations help.</p><p>First, require at least one high-selectivity filter to be present. A query with no date range and no status filter that scans the full order history is not a filter query  it&#8217;s an accidental full table scan. Validate that at least one indexed column is constrained before executing.</p><p>Second, expose date range limits on endpoints that touch time-series data. A maximum date range of 90 days on the order history endpoint is not arbitrary it&#8217;s a budget control. Document it in the API contract so the product team designs their UI accordingly.</p><p>Third, use Snowflake&#8217;s query timeout setting to cap runaway queries, independent of the Lambda and API Gateway timeouts.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;2372e45a-3de2-45ae-ab3c-8987e00b3225&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Set per-query timeout before execution

cursor.execute(&#8221;ALTER SESSION SET STATEMENT_TIMEOUT_IN_SECONDS = 20&#8221;)
cursor.execute(query, params)</code></pre></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qmsN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qmsN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!qmsN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!qmsN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!qmsN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qmsN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1236572,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/199571661?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qmsN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!qmsN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!qmsN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!qmsN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d4e58fe-1fad-43ae-aab9-ccb82daa3140_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h4>Passing RBAC from the API Layer to Snowflake</h4><p>Access control is the part of data API design that gets designed last and retrofitted worst. The default pattern in most first versions is a single Snowflake service account that sees everything, with the API layer handling row filtering in the WHERE clause. That works until the data governance team asks how you know user A can&#8217;t see user B&#8217;s data, and the answer is &#8220;because we add a WHERE clause in the Lambda handler.&#8221;</p><p>That&#8217;s not access control it&#8217;s a filter that could be accidentally omitted or bypassed. Snowflake has native mechanisms for enforcing data visibility at the warehouse layer, independent of what the API sends.</p><h5>Pattern 1 - Application-Level Filtering (Simple, Brittle)</h5><p>Every query includes a tenant or user filter derived from the authenticated request context. The API layer extracts the user&#8217;s tenant ID from the JWT token and injects it into every query.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;dfab035f-fd36-42da-bbbe-1ebd0ec69156&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql"># Extract tenant from authenticated token
tenant_id = jwt_payload[&#8221;tenant_id&#8221;]
user_roles = jwt_payload[&#8221;roles&#8221;]

# Injected into every query
query = f&#8221;&#8220;&#8221;
SELECT * FROM orders
WHERE tenant_id = %s
  AND [filter conditions]
ORDER BY order_date DESC
LIMIT %s
&#8220;&#8221;&#8220;
params = [tenant_id, ...]</code></pre></div><p>This works and is simple to implement. The fragility: every query path must include the tenant filter. A new endpoint that forgets it exposes data to the wrong tenant. There&#8217;s no enforcement at the database layer. If the API is compromised or has a bug, Snowflake has no independent protection.</p><h5>Pattern 2 - Snowflake Row Access Policies (Enforced at the Data Layer)</h5><p>Snowflake Row Access Policies (RAP) enforce row-level visibility at the table level, regardless of what the API sends. The policy is applied to the table once. Every query against that table - from the API, from a data analyst, from a notebook - passes through the policy. There is no way to accidentally bypass it from the application layer.</p><p>The key is passing the user&#8217;s context into the Snowflake session before executing queries. Snowflake provides `SYSTEM$CONTEXT` and custom session parameters for this.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;103ad841-1fef-43ed-ad7e-50cbc248faa5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Set user context on the Snowflake session before executing queries

def execute_with_context(cursor, user_context: dict, query: str, params: list):
    # Set session-level context variables that the row access policy reads
    cursor.execute(
        &#8220;ALTER SESSION SET QUERY_TAG = %s&#8221;,
        [f&#8221;api_user:{user_context[&#8217;user_id&#8217;]}|tenant:{user_context[&#8217;tenant_id&#8217;]}&#8221;]
    )

    cursor.execute(
        &#8220;CALL SET_SESSION_CONTEXT(&#8217;current_tenant_id&#8217;, %s)&#8221;,
        [user_context[&#8221;tenant_id&#8221;]]
    )

    cursor.execute(
        &#8220;CALL SET_SESSION_CONTEXT(&#8217;user_role&#8217;, %s)&#8221;,
        [user_context[&#8221;role&#8221;]]
    )
    # Now execute the query - RAP will apply based on the session context
    cursor.execute(query, params)</code></pre></div><p>The Row Access Policy on the Snowflake side reads the session context and filters rows accordingly:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;aa7585bb-f9c7-464e-b305-2bb0d8fd0781&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">-- Row Access Policy applied to the orders table

CREATE OR REPLACE ROW ACCESS POLICY tenant_isolation_policy
  AS (row_tenant_id VARCHAR) RETURNS BOOLEAN -&gt;
    CASE
      -- Service account with elevated access sees all rows
      WHEN CURRENT_USER() = &#8216;API_ADMIN_ROLE&#8217; THEN TRUE
      -- API users see only their tenant&#8217;s rows
      WHEN SYSTEM$CONTEXT(&#8217;current_tenant_id&#8217;) = row_tenant_id THEN TRUE
      ELSE FALSE
    END;

-- Apply to the table

ALTER TABLE orders
  ADD ROW ACCESS POLICY tenant_isolation_policy ON (tenant_id);</code></pre></div><p>With this in place, even if the API handler omits the tenant filter in the WHERE clause, Snowflake&#8217;s RAP blocks cross-tenant data access. The policy is enforced at scan time - rows that fail the policy aren&#8217;t returned, not even to the query optimizer.</p><h5>Pattern 3 - Snowflake Column Masking Policies (Field-Level Access)</h5><p>Row access policies control which rows a user sees. Column masking policies control which fields they see in full. This is relevant for data APIs that expose tables containing PII - email addresses, phone numbers, national ID numbers where some roles see the full value and others see a masked version.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;b2c6d0a0-ee58-4359-9daa-2bf7edc27501&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">-- Column masking policy for email field

CREATE OR REPLACE MASKING POLICY email_masking_policy
  AS (val STRING) RETURNS STRING -&gt;
    CASE
      WHEN SYSTEM$CONTEXT(&#8217;user_role&#8217;) IN (&#8217;ADMIN&#8217;, &#8216;COMPLIANCE&#8217;) THEN val
      WHEN SYSTEM$CONTEXT(&#8217;user_role&#8217;) = &#8216;ANALYST&#8217; THEN REGEXP_REPLACE(val, &#8216;.+@&#8217;, &#8216;***@&#8217;)
      ELSE &#8216;***MASKED***&#8217;
    END;

-- Apply to the column

ALTER TABLE customers
  MODIFY COLUMN email SET MASKING POLICY email_masking_policy;</code></pre></div><p>The API returns the masking policy&#8217;s output. The API handler doesn&#8217;t need to know anything about which fields are masked for which roles that logic lives in Snowflake and applies universally.</p><h4>Connecting the API Auth Layer to Snowflake Context</h4><p>The full pattern from API request to Snowflake access control:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;f6e6741d-a12a-4a19-a77e-e7f62d3dc9b3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import jwt
def handler(event, context):
    # 1. Validate the incoming JWT from API Gateway authorizer

    token = event[&#8221;headers&#8221;][&#8221;Authorization&#8221;].replace(&#8221;Bearer &#8220;, &#8220;&#8221;)
    payload = jwt.decode(token, PUBLIC_KEY, algorithms=[&#8221;RS256&#8221;])
    user_context = {
        &#8220;user_id&#8221;: payload[&#8221;sub&#8221;],
        &#8220;tenant_id&#8221;: payload[&#8221;tenant_id&#8221;],
        &#8220;role&#8221;: payload[&#8221;role&#8221;]          # e.g., &#8220;ANALYST&#8221;, &#8220;ADMIN&#8221;, &#8220;VIEWER&#8221;
    }

    # 2. Parse and validate the filter request

    body = json.loads(event[&#8221;body&#8221;])
    validate_filters(body[&#8221;filters&#8221;])     # raises if invalid

    # 3. Execute with Snowflake context set

    cursor = get_connection().cursor()
    execute_with_context(cursor, user_context, query, params)

    # 4. Snowflake&#8217;s RAP and masking policies enforce the rest

    rows = cursor.fetchall()
    return format_response(rows, cursor.description)</code></pre></div><p>The API layer handles authentication (JWT validation) and filter validation. Snowflake handles data access enforcement (RAP, column masking). Neither layer trusts the other to do the other&#8217;s job.</p><h4>Which Pattern to Use</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6IGT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6IGT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!6IGT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!6IGT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!6IGT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6IGT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1346238,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/199571661?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6IGT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!6IGT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!6IGT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!6IGT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25315f70-8ce9-4998-b498-502afe96e6d2_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YLT_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YLT_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!YLT_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!YLT_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!YLT_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YLT_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1236485,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/199571661?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YLT_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!YLT_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!YLT_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!YLT_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7e230f9-897d-4346-8947-e66554e0fbe0_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h4>Caching and Rate Limiting at the API Layer</h4><p>Caching keeps queries from reaching Snowflake unnecessarily. Rate limiting keeps Snowflake from absorbing the consequences of badly behaved clients. Both are protection mechanisms, and both are easier to add before a product team&#8217;s traffic hits the API than after.</p><h5>Caching Layers</h5><p>Snowflake caches query results for 24 hours when the query text is identical and the underlying data hasn&#8217;t changed. The same Snowflake role must be used, and session parameters that affect query semantics (timezone, date format) must be consistent any variation causes a cache miss even if the logical query is the same. Normalizing how you construct SQL and initialize the connector keeps this cache working correctly without extra effort.</p><p>Note that when session context variables are used for RBAC, the query text includes those context-setting statements. Two users from different tenants executing the same query will not share a Snowflake result cache hit  the session context differs, so Snowflake treats the queries as distinct. This is correct behavior, but it means Snowflake&#8217;s result cache is less effective in multi-tenant deployments.</p><p>API Gateway response caching applies to GET endpoints. You configure a TTL per route and API Gateway serves cached responses without invoking Lambda. Reference data that updates on a daily batch schedule can tolerate a multi-hour TTL. The cache key is the full URL including query parameters, so parameterized GET endpoints get per-parameter-set caching automatically.</p><p>For POST query endpoints, API-layer caching requires explicit design. The request body hash serves as the cache key, with Redis or Elasticache as the store. In a multi-tenant system, the cache key must include the tenant ID and user role, not just the filter hash  two users from different tenants with identical filter payloads should not share a cache entry.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;554b8d94-acb5-4c13-86ff-a39881beb8e9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import hashlib, json

def get_cache_key(user_context: dict, request_body: dict) -&gt; str:
    # Include tenant and role in key - different contexts can&#8217;t share cache entries
    key_components = {
        &#8220;tenant_id&#8221;: user_context[&#8221;tenant_id&#8221;],
        &#8220;role&#8221;: user_context[&#8221;role&#8221;],
        &#8220;filters&#8221;: request_body
    }

    normalized = json.dumps(key_components, sort_keys=True)
    return f&#8221;query:{hashlib.sha256(normalized.encode()).hexdigest()}&#8221;</code></pre></div><p>The TTL must match the data&#8217;s update cadence. Caching transactional data with a long TTL will produce stale results that product teams file as data quality issues.</p><h5>Rate Limiting</h5><p>Rate limiting serves two purposes. It protects Snowflake from a single badly-behaved client consuming all available warehouse capacity. It also ensures fair access across multiple product teams - one team&#8217;s overnight batch report shouldn&#8217;t degrade interactive dashboard queries from another team.</p><p>API Gateway usage plans provide token bucket rate limiting per API key. A token bucket replenishes at a configured rate and allows short bursts above the steady-state limit. That burst capacity is important for clients that make occasional spiky requests; without it, a momentary spike triggers throttling that affects normal usage.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;yaml&quot;,&quot;nodeId&quot;:&quot;99c01a8e-b82a-4cae-9f70-7232793c50f0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-yaml"># CloudFormation usage plan
Throttle:
  RateLimit: 10       # requests per second steady state
  BurstLimit: 50      # burst capacity above steady state
Quota:
  Limit: 10000
  Period: DAY</code></pre></div><p>For production APIs, per-endpoint throttle settings are more precise than a single plan across all routes. A lightweight lookup endpoint can tolerate a much higher request rate than a full aggregation. API Gateway supports per-method throttling.</p><p>When a rate limit is hit, the response must include a `Retry-After` header specifying when the client can retry. Without it, clients without exponential backoff will retry immediately and sustain the throttling. A well-behaved client that respects `Retry-After` turns a rate limit event into a brief pause. A client that ignores it turns it into a sustained traffic storm.</p><div><hr></div><h4>Pagination Patterns for Large Result Sets</h4><p>Pagination is the single design decision that breaks most visibly under scale, and the one most first-version APIs skip or get wrong. The choice determines the Snowflake query structure, the client contract, and the performance characteristics under concurrent load. Retrofitting it after a product team has built against your API requires a versioned contract change.</p><h4>Offset Pagination</h4><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;6df52923-c89c-4f08-98d7-c0452240a966&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql"># Request
GET /orders?page=3&amp;page_size=100

# Lambda generates:
SELECT * FROM orders
WHERE [filter conditions]
ORDER BY order_date DESC
LIMIT 100 OFFSET 200</code></pre></div><p>Offset pagination is intuitive to build and familiar to clients. The problem is what it costs Snowflake at scale. `OFFSET 200` requires computing 200 rows before returning the next 100. `OFFSET 10000` means 10,000 rows scanned and discarded. Multiple clients paginating concurrently each run the full query with their respective offsets. On large datasets with complex queries, that overhead accumulates quickly.</p><p>Use offset pagination when the maximum result set is small and stable enough that the scan cost doesn&#8217;t accumulate - reference data, lookup tables, datasets with bounded row counts in the thousands.</p><h4>Cursor-Based Pagination</h4><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;28122f95-0850-45e5-870e-54afaa8cba30&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># First request

POST /orders/query
{

    &#8220;filters&#8221;: {&#8221;and&#8221;: [{&#8221;field&#8221;: &#8220;status&#8221;, &#8220;op&#8221;: &#8220;in&#8221;, &#8220;value&#8221;: [&#8221;pending&#8221;, &#8220;processing&#8221;]}]},
    &#8220;page_size&#8221;: 100
}

# Response
{
    &#8220;data&#8221;: [...],
    &#8220;next_cursor&#8221;: &#8220;eyJvcmRlcl9kYXRlIjoiMjAyNC0wMy0xNSIsIm9yZGVyX2lkIjo4ODczNH0=&#8221;,
    &#8220;has_more&#8221;: true
}

# Subsequent request - same filters + cursor

POST /orders/query
{
    &#8220;filters&#8221;: {&#8221;and&#8221;: [{&#8221;field&#8221;: &#8220;status&#8221;, &#8220;op&#8221;: &#8220;in&#8221;, &#8220;value&#8221;: [&#8221;pending&#8221;, &#8220;processing&#8221;]}]},
    &#8220;page_size&#8221;: 100,
    &#8220;cursor&#8221;: &#8220;eyJvcmRlcl9kYXRlIjoiMjAyNC0wMy0xNSIsIm9yZGVyX2lkIjo4ODczNH0=&#8221;
}

# Lambda decodes the cursor and generates:

SELECT * FROM orders
WHERE status IN (&#8217;pending&#8217;, &#8216;processing&#8217;)
  AND (order_date &lt; &#8216;2024-03-15&#8217;
    OR (order_date = &#8216;2024-03-15&#8217; AND order_id &lt; 88734))
ORDER BY order_date DESC, order_id DESC
LIMIT 100</code></pre></div><p>The cursor encodes the sort position of the last record seen. The next query becomes a range filter against indexed columns, not an offset scan. Concurrent clients each carry their own cursor - their queries don&#8217;t interfere.</p><p>Cursor design requirements: the cursor must encode all dimensions needed to reconstruct the position uniquely, including the primary key as a tiebreaker. <code>ORDER BY order_date</code> without a tiebreaker produces unstable cursors when multiple records share the same date. The cursor should be opaque to clients - base64-encoded JSON is standard. The sort order must be deterministic and stable across the lifetime of a pagination session.</p><h4>Keyset Pagination</h4><p>Keyset pagination exposes the sort position directly rather than encoding it in a token. It generates the same efficient range filter as cursor pagination but constrains the API to a fixed, client-visible sort key. Changing the sort strategy is a breaking change. It works well for time-series data with a natural, stable ordering key.</p><p>For most Snowflake data APIs, cursor-based pagination is the right default. Efficient, handles concurrent clients, keeps the sort strategy internal.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UN5M!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UN5M!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!UN5M!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!UN5M!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!UN5M!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UN5M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1264114,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/199571661?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UN5M!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!UN5M!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!UN5M!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!UN5M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74a76e1-ed21-4b7e-bbc6-72648c4a2773_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h3>Async Query Handling for Heavy Snowflake Queries</h3><p>Some Snowflake queries are incompatible with a synchronous request-response. Multi-year aggregations, cross-dataset reconciliations, complex window function computations across large partitions - these take minutes, not seconds. The 29-second API Gateway limit is not going to accommodate them.</p><p>Before reaching for an async architecture, profile actual query runtimes on the target warehouse at the right size. A query that takes 45 seconds on an X-Small warehouse might run in 6 seconds on a Medium. Async adds real complexity. It should be the last option considered after warehouse sizing and query optimization, not the first response to a slow endpoint.</p><p>When the query&#8217;s runtime genuinely exceeds the synchronous budget after proper sizing, the async pattern divides one endpoint into three.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;markdown&quot;,&quot;nodeId&quot;:&quot;32a81ec2-ad89-47c0-9aa1-ed45abeb837a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-markdown">POST /reports/query          &#8594; Submit: validate, create job, return job ID (202)
GET  /reports/jobs/{job_id}  &#8594; Status: check job state (pending / running / complete / failed)
GET  /reports/jobs/{job_id}/result  &#8594; Result: return completed data</code></pre></div><p><strong>Submit endpoint: </strong>Validates the request, generates a job ID, writes job state to DynamoDB, and triggers the query Lambda asynchronously. Returns 202 Accepted with the job ID and a polling URL. The client is now responsible for polling.</p><p><strong>Query Lambda (async invocation):</strong> Updates job state to &#8220;running,&#8221; executes the Snowflake query, writes the result to S3, and updates job state to &#8220;complete&#8221; with the S3 key and row count. On failure, writes the error to job state so the status endpoint can surface it.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;6d65f25f-288a-4c3f-a40a-98c4579b39b5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># DynamoDB job state schema
{

    &#8220;job_id&#8221;: &#8220;f8a2c1d4-...&#8221;,
    &#8220;status&#8221;: &#8220;complete&#8221;,                   # pending | running | complete | failed
    &#8220;created_at&#8221;: &#8220;2024-03-15T14:23:00Z&#8221;,
    &#8220;completed_at&#8221;: &#8220;2024-03-15T14:25:47Z&#8221;,
    &#8220;request_params&#8221;: {...},
    &#8220;s3_key&#8221;: &#8220;results/f8a2c1d4/result.json&#8221;,
    &#8220;row_count&#8221;: 42380,
    &#8220;error&#8221;: null,
    &#8220;ttl&#8221;: int(time.time()) + (86400 * 7)   # DynamoDB TTL for automatic cleanup
}
</code></pre></div><p>For medium-sized result sets that don&#8217;t need S3 staging, the Snowflake Python connector&#8217;s native async query capability <code>execute_async()</code> is a simpler alternative. It returns a query ID that can be polled directly via the connector without S3 as an intermediate store.</p><p>The organizational implication of async: product teams building clients against an async endpoint need to implement polling with exponential backoff. That conversation should happen before development starts.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_2Gn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_2Gn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!_2Gn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!_2Gn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!_2Gn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_2Gn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1298362,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/199571661?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_2Gn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!_2Gn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!_2Gn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!_2Gn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93e8011-b273-48ee-a938-cd67e82562d4_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h3>End-to-End Design: From Snowflake to Frontend UI</h3><p>Most of this article covers the API layer. The harder question in practice is how the full stack fits together - from Snowflake data to a product team&#8217;s dashboard or data table in a frontend UI. The design choices at the API layer have direct consequences for what the UI can and can&#8217;t do efficiently.</p><h4>What the Frontend Actually Needs</h4><p>A frontend data table or dashboard has three typical behaviors that translate to distinct API patterns.</p><p><strong>Initial load:</strong> The user opens the dashboard. The UI fetches the first page of data or a summary aggregation. This needs to be fast ideally under 2 seconds. For summary data (totals, aggregates), the right source is a pre-computed materialized view or Snowflake Dynamic Table, not a real-time aggregation query. For tabular data, the first page via cursor pagination with a short cache TTL works.</p><p><strong>Filter interaction:</strong> The user applies filters. The UI sends the filter payload and expects a response fast enough to feel interactive typically under 3 seconds. This is where the filter DSL from the earlier section earns its value. A well-structured filter payload is cacheable (with body-hash keys), translates to efficient parameterized SQL, and returns a consistent response shape regardless of which filters are active.</p><p><strong>Pagination / infinite scroll:</strong> The user scrolls or clicks &#8220;next page.&#8221; The UI sends the cursor from the previous response. The response time should match the initial load. With cursor-based pagination, this is consistent the cursor query is a range filter, not an offset scan, so page 50 loads as fast as page 1.</p><h4>Pre-Compute What the Dashboard Will Always Show</h4><p>The most expensive data API mistake for frontend use cases is treating every dashboard widget as a live, on-demand Snowflake query. Real-time queries against large tables for every user who opens the dashboard is a credit burn and a latency problem.</p><p>Snowflake Dynamic Tables and materialized views solve this at the data layer. Define the aggregation once, let Snowflake refresh it on a schedule, and have the API read from the pre-computed table. The query the API executes becomes a simple `SELECT` against a small, pre-aggregated table rather than a full scan with GROUP BY across millions of rows.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;81bcc327-a306-4b04-ab43-eb70998a9426&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">-- Dynamic Table for dashboard KPIs - refreshed every 15 minutes

CREATE OR REPLACE DYNAMIC TABLE dashboard_order_summary
  TARGET_LAG = &#8216;15 minutes&#8217;
  WAREHOUSE = REPORTING_WH
AS
SELECT
    region_code,
    order_status,
    DATE_TRUNC(&#8217;day&#8217;, created_date) AS order_day,
    COUNT(*) AS order_count,
    SUM(order_value_usd) AS total_value,
    AVG(fulfillment_hours) AS avg_fulfillment_hours
FROM orders
WHERE created_date &gt;= DATEADD(&#8217;day&#8217;, -90, CURRENT_DATE())
GROUP BY 1, 2, 3;</code></pre></div><p>The API endpoint that serves the dashboard summary hits this Dynamic Table, not the orders table directly. Response time drops to milliseconds. Cache TTL can be set to 15 minutes to match the refresh cadence. Snowflake credit consumption is decoupled from dashboard traffic.</p><h4>The Frontend API Contract</h4><p>A frontend-facing data API needs a few specific behaviors that differ from internal or batch-consumer APIs.</p><p><strong>Consistent response shape:</strong> The UI depends on a stable response schema. Field renames, type changes, or null handling differences between data conditions break the UI silently. Define a strict response schema and version it explicitly. Return nulls rather than omitting missing fields.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;5eba1265-209c-47ea-b2f6-388b755a49e2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json"># Consistent response envelope - same shape regardless of result count
{
    &#8220;data&#8221;: [
        {
            &#8220;order_id&#8221;: &#8220;ORD-88734&#8221;,
            &#8220;order_date&#8221;: &#8220;2024-03-15&#8221;,
            &#8220;status&#8221;: &#8220;processing&#8221;,
            &#8220;total_value&#8221;: 1250.00,
            &#8220;customer_name&#8221;: &#8220;Acme Corp&#8221;
        }
    ],
    &#8220;pagination&#8221;: {
        &#8220;next_cursor&#8221;: &#8220;eyJvcmRlcl9kYXRlIjoiMjAy...&#8221;,
        &#8220;has_more&#8221;: true,
        &#8220;total_count&#8221;: null          # omit or null - expensive to compute exactly
    },

    &#8220;meta&#8221;: {
        &#8220;query_id&#8221;: &#8220;01b3c4d5-...&#8221;,
        &#8220;execution_ms&#8221;: 234,
        &#8220;cache_hit&#8221;: false,
        &#8220;data_as_of&#8221;: &#8220;2024-03-15T14:00:00Z&#8221;   # when the source data was last refreshed
    }
}
</code></pre></div><p><strong>Total count is almost always wrong:</strong> UI components often want to display &#8220;Showing 100 of 42,380 results.&#8221; That `total_count` requires a separate COUNT(*) query against the full filtered dataset. On large tables with complex filters, that count query can be slower than the data query itself. Options: omit total count and use &#8220;has_more&#8221; for pagination UI, return an approximate count using HyperLogLog, or accept the cost for endpoints where the count is genuinely valuable to the user.</p><p><strong>Data freshness indicators:</strong> A dashboard sourced from pre-computed aggregates has a lag the data is 15 minutes old or refreshed nightly. The API should surface this in the response meta. The UI should display it to users. &#8220;As of 2 hours ago&#8221; is information the user needs to interpret the data correctly. Hiding it creates trust problems when users compare API output to live Snowflake queries and find discrepancies.</p><p><strong>Error handling that the UI can act on:</strong> A 504 timeout should tell the UI which endpoint to use instead (the async endpoint, if one exists). A 429 rate limit response should include `Retry-After` so the UI can implement an automatic retry with a visible loading state rather than showing an error. A 403 from the RBAC layer should return a consistent error code that the UI maps to a &#8220;you don&#8217;t have access to this data&#8221; message, not a generic &#8220;something went wrong.&#8221;</p><h3>The Full Stack Architecture</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;markdown&quot;,&quot;nodeId&quot;:&quot;21949798-ac97-4392-869e-19c6a9af2a01&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-markdown">Frontend UI
    |
    | HTTPS (JWT in Authorization header)
    v
API Gateway
    &#9500;&#9472;&#9472; Auth: Lambda Authorizer validates JWT, extracts user context
    &#9500;&#9472;&#9472; Caching: GET endpoints cached by URL+TTL
    &#9500;&#9472;&#9472; Rate limiting: per-client token bucket
    |
    v
Lambda Function
    &#9500;&#9472;&#9472; Filter validation: allowlist, operator check, parameterized SQL
    &#9500;&#9472;&#9472; Context injection: set Snowflake session vars (tenant, role)
    &#9500;&#9472;&#9472; Cache check: Redis hit &#8594; return immediately
    &#9500;&#9472;&#9472; Query execution &#8594; Snowflake
    |
    v
Snowflake
    &#9500;&#9472;&#9472; Row Access Policy: enforces tenant + role visibility at scan time
    &#9500;&#9472;&#9472; Column Masking Policy: masks PII fields based on session context role
    &#9500;&#9472;&#9472; Dynamic Tables / Materialized Views: pre-computed aggregates
    &#9500;&#9472;&#9472; Result cache: 24h cache for repeated identical queries
    |
    &#9492;&#9472;&#9472; Returns row-filtered, field-masked result set

Lambda
    &#9500;&#9472;&#9472; Structures result into response envelope
    &#9500;&#9472;&#9472; Logs Snowflake query ID + execution time
    &#9492;&#9472;&#9472; Writes to Redis cache with appropriate TTL

API Gateway
    &#9492;&#9472;&#9472; Returns response to frontend
</code></pre></div><p>The frontend doesn&#8217;t know about Snowflake. It sends JWTs and filter payloads. It receives a consistent response envelope with data, pagination cursors, and freshness metadata. The data access control lives in Snowflake not in application code that can be accidentally omitted.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gkBn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gkBn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!gkBn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!gkBn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!gkBn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gkBn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1372468,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/199571661?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gkBn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!gkBn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!gkBn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!gkBn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2a262c4-8b99-478d-a0e2-d97e313aad87_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>What Production Actually Requires</h3><p>The patterns above address the main failure modes. Two operational concerns complete the picture and are consistently missing from first-version APIs.</p><p><strong>Connection management: </strong> Lambda is stateless between invocations, but the Snowflake connection initialized at module level persists across warm invocations within the same execution environment.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;8e5fcc41-30d5-435f-af91-2dc54111f81d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Initialize at module level - persists across warm invocations
conn = snowflake.connector.connect(
    user=os.environ[&#8221;SF_USER&#8221;],
    password=os.environ[&#8221;SF_PASSWORD&#8221;],
    account=os.environ[&#8221;SF_ACCOUNT&#8221;],
    warehouse=os.environ[&#8221;SF_WAREHOUSE&#8221;],
    database=os.environ[&#8221;SF_DATABASE&#8221;],
    schema=os.environ[&#8221;SF_SCHEMA&#8221;]
)

def get_connection():
    global conn
    try:
        conn.cursor().execute(&#8221;SELECT 1&#8221;)
    except Exception:
        conn = snowflake.connector.connect(**connection_params)
    return conn

def handler(event, context):
    cursor = get_connection().cursor()
    ...</code></pre></div><p>A module-level connection reuses established sessions across warm invocations, reducing per-request overhead significantly. The health check handles connections that go stale after extended idle periods.</p><p><strong>Observability:</strong> Every request should emit a structured log event with enough to answer &#8220;what did this endpoint actually do?&#8221; for any request.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;00039bd6-1950-4385-b3e7-0ed261dea222&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">logger.info({
    &#8220;request_id&#8221;: context.aws_request_id,
    &#8220;user_id&#8221;: user_context[&#8221;user_id&#8221;],
    &#8220;tenant_id&#8221;: user_context[&#8221;tenant_id&#8221;],
    &#8220;endpoint&#8221;: &#8220;/orders/query&#8221;,
    &#8220;snowflake_query_id&#8221;: cursor.sfqid,
    &#8220;query_execution_ms&#8221;: query_time,
    &#8220;result_row_count&#8221;: row_count,
    &#8220;cache_hit&#8221;: cache_hit,
    &#8220;filter_count&#8221;: len(body.get(&#8221;filters&#8221;, {}).get(&#8221;and&#8221;, []))
})</code></pre></div><p>The Snowflake query ID links an API response to the Snowflake query history panel. With it, you find the query execution plan in under a minute. Without it, you correlate timestamps and hope.</p><p>Error responses need a consistent schema. A 500 with an opaque message tells the client nothing actionable.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;f96c8b7b-59a8-4076-b7a5-cc322c3d32ce&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">{
    &#8220;error&#8221;: {
        &#8220;code&#8221;: &#8220;QUERY_TIMEOUT&#8221;,
        &#8220;message&#8221;: &#8220;Query exceeded the execution limit. For large date ranges, use the /reports/query async endpoint.&#8221;,
        &#8220;query_id&#8221;: &#8220;01b3c4d5-...&#8221;,
        &#8220;request_id&#8221;: &#8220;req_abc123&#8221;
    }
}</code></pre></div><h3>Three Things Teams Get Wrong</h3><p>These organizational failure modes repeat across data API projects. None of them are technology problems.</p><h4>Treating API design as a data team concern only</h4><p>The product team&#8217;s client behavior polling frequency, retry logic, pagination implementation, filter payload construction directly affects warehouse performance. The rate limiting contract, pagination model, and async interface need to be agreed upon before their clients are built, not retrofitted after habits form.</p><h4>Designing access control as an afterthought</h4><p>Access control designed after the fact usually becomes a WHERE clause injected into existing queries. That&#8217;s brittle, invisible to auditors, and bypassed by every new endpoint that forgets to include it. Design the Snowflake Row Access Policy and column masking policies before the first tenant goes live. Adding them later requires testing every query path against the new policies which nobody has time to do correctly under production pressure.</p><h4>Conflating &#8220;it works in testing&#8221; with &#8220;production-ready.&#8221;</h4><p>Test conditions small datasets, single concurrent client, narrow date ranges are designed to make the API work. Production conditions are designed to find where it doesn&#8217;t. Load testing with realistic query profiles, concurrent clients, and multi-tenant workloads before traffic arrives is not optional.</p><div><hr></div><h3>Where to Start</h3><p>Don&#8217;t try to build all of this at once. The sequence matters.</p><p>Get pagination and the filter schema right first. Both require contract changes to fix after clients have built against them. Cursor pagination and a typed filter DSL are not late optimizations they&#8217;re foundation decisions.</p><p>Set up Snowflake Row Access Policies and session context before multi-tenant data goes to any non-admin user. If you don&#8217;t have RAP from the start, you&#8217;ll add it under pressure after a governance review. Adding it under pressure means rushing the testing.</p><p>Add rate limiting before the first external client goes live. It&#8217;s a configuration change on API Gateway, not a code change, and it caps the downside risk before any usage patterns are established.</p><p>Pre-compute aggregates for any dashboard-style workloads using Dynamic Tables or materialized views. Don&#8217;t wait for the product team to report that the dashboard is slow. The cost of real-time aggregation against a large table is obvious in load testing and invisible in development.</p><p>Treat async as an opt-in for specific endpoints that genuinely exceed the synchronous budget after query optimization. Don&#8217;t apply it universally.</p><p>A production data API&#8217;s first obligation is predictable behavior. Predictable response times. Predictable access control. Predictable cost to the warehouse. That&#8217;s what product teams can build on. Unpredictable is what produces incidents and erodes trust in the data layer.</p><div><hr></div><h4>REFERENCES</h4><ul><li><p><a href="https://docs.snowflake.com/en/developer-guide/python-connector/python-connector">Snowflake Python Connector documentation:</a></p></li><li><p><a href="https://docs.snowflake.com/en/user-guide/querying-persisted-results">Snowflake query result caching</a></p></li><li><p><a href="https://docs.snowflake.com/en/developer-guide/python-connector/python-connector-example#using-execute-async-to-run-queries-asynchronously">Snowflake async query execution (execute_async)</a></p></li><li><p><a href="https://docs.snowflake.com/en/user-guide/security-row-intro">Snowflake Row Access Policies</a></p></li><li><p><a href="https://docs.snowflake.com/en/user-guide/security-column-ddm-intro">Snowflake Column Masking Policies</a></p></li><li><p><a href="https://docs.snowflake.com/en/user-guide/dynamic-tables-intro">Snowflake Dynamic Tables</a></p></li><li><p><a href="https://docs.snowflake.com/en/sql-reference/functions/system_context">Snowflake Session Context (SYSTEM$CONTEXT)</a></p></li><li><p><a href="https://www.ietf.org/archive/id/draft-ietf-httpbis-safe-method-w-body-05.txt">IETF HTTP QUERY Method </a></p></li><li><p><a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html">AWS API Gateway integration timeout limits</a></p></li><li><p><a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html">AWS API Gateway caching</a></p></li><li><p><a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html">AWS API Gateway usage plans and throttling</a></p></li><li><p><a href="https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html">AWS Lambda provisioned concurrency</a></p></li><li><p><a href="https://slack.engineering/evolving-api-pagination-at-slack/">Cursor-based pagination patterns (Slack Engineering)</a></p></li></ul><p></p>]]></content:encoded></item><item><title><![CDATA[The Python Developer’s Field Guide To Copilot Spaces]]></title><description><![CDATA[A practical guide to using GitHub Copilot Spaces to organize project context, support Python team workflows, and get suggestions grounded in your actual codebase.]]></description><link>https://ganeshkedari.substack.com/p/the-python-developers-field-guide</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/the-python-developers-field-guide</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Tue, 26 May 2026 17:47:07 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/488acc32-2cde-4472-8ce1-6ffdb63c0ef0_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You set up Copilot, wrote a few prompts, and got code back quickly. That was the easy part. Then you actually looked at what it generated.</p><p>The function signatures were close. The logic was plausible. But the code didn&#8217;t use your Pydantic models. It wrote unittest-style test cases when your whole team uses pytest. It imported a library your virtual environment doesn&#8217;t have. And it completely ignored the custom base handler every endpoint in your service inherits from.</p><p>That&#8217;s not a Copilot problem. It&#8217;s a context problem. Copilot doesn&#8217;t know your project &#8212; it knows Python. Those are different things, and on an opinionated codebase, the gap between the two is where the time goes.</p><p>GitHub Copilot Spaces, generally available since September 2025, is built for exactly this. A Space lets you curate the context Copilot uses: your architecture docs, your base classes, your pytest setup, your open issues. When it works well, the suggestions come back grounded in how your project actually works, not averaged across all the Python code Copilot has ever seen.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!L6IL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!L6IL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!L6IL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!L6IL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!L6IL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!L6IL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1525484,&quot;alt&quot;:&quot;Two-panel cartoon comparing a frustrated developer with generic Copilot suggestions versus a confident developer with project-grounded Copilot output&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/198866997?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Two-panel cartoon comparing a frustrated developer with generic Copilot suggestions versus a confident developer with project-grounded Copilot output" title="Two-panel cartoon comparing a frustrated developer with generic Copilot suggestions versus a confident developer with project-grounded Copilot output" srcset="https://substackcdn.com/image/fetch/$s_!L6IL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!L6IL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!L6IL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!L6IL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9f709ef-342a-4aaf-9d79-5343c4b7d12a_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p></p><h2>The Context Problem in Python Projects</h2><p>Python is flexible by design. That flexibility is useful for individual developers and a consistency challenge for teams. Every Python codebase develops its own idioms: how dependency injection is handled, how models are validated, how tests are structured, which libraries are in-scope. These patterns are the product of decisions made over months &#8212; they reduce bugs, enforce predictability, and make the codebase legible to the next person.</p><p>Copilot without project context generates against the aggregate of publicly available Python code, not your team&#8217;s particular conventions. I&#8217;ve seen this play out repeatedly on data pipeline teams. A new engineer asks Copilot to add a FastAPI endpoint. The result is code that works, but it bypasses the project&#8217;s request validation layer, uses a raw dict instead of the Pydantic response model, and writes test assertions that don&#8217;t match the fixture setup in conftest.py. Three rounds of PR feedback later, the code is right. The AI didn&#8217;t save time &#8212; it moved the correction work to a later step.</p><div class="pullquote"><p>The prompt wasn&#8217;t the problem. The context was.</p></div><h3>What Copilot Spaces Actually Does</h3><p>A Space is a project-specific knowledge bundle. You curate what goes into it and Copilot uses it when responding.</p><p>The retrieval mechanism has two modes, and the distinction matters in practice. When you attach a <em><strong>repository</strong></em>, Copilot searches it and retrieves sections relevant to your query &#8212; it doesn&#8217;t load the entire codebase into memory. This works well for broad questions about the project. When you attach <em><strong>individual files</strong></em> directly, their full contents load into Copilot&#8217;s context window on every query. This is the mechanism for ensuring your most important patterns are always available.</p><p>Every Space also has an <em><strong>Instructions panel</strong></em> &#8212; free-text behavioral guidance for how Copilot should work inside this project. Which framework, which testing approach, which naming conventions. Think of it as the standing brief you&#8217;d give a smart contractor joining the team.</p><p>Sources linked to a Space stay in sync automatically. Repositories update as the codebase evolves. You don&#8217;t manually refresh anything.</p><h3>What to Put in a Python Space</h3><p>For a typical Python backend service, a well-built Space contains:</p><ol><li><p><strong>The repository connection: </strong> This covers broad retrieval for general questions about the codebase. Copilot searches it on demand, retrieving relevant sections &#8212; not the whole project.</p></li><li><p><strong>A small set of directly attached files</strong>: These load fully on every query. Keep the list focused. For most Python projects, the high-value files are:</p><ol><li><p><code>pyproject.toml</code> or <code>requirements.txt</code> &#8212; what&#8217;s actually installed and configured</p></li><li><p>Your base class or base handler &#8212; the pattern every new component should imitate</p></li><li><p>Your main Pydantic models file &#8212; so Copilot understands your data shapes</p></li><li><p><code>tests/conftest.py</code> &#8212; your pytest fixtures and test setup</p></li></ol><p></p><p>Five to eight files are usually enough. The goal is to give Copilot the patterns it should imitate, not to recreate the whole codebase as attachments.</p></li><li><p><strong>The Instructions panel :</strong>  More on this next.</p></li><li><p><strong>Linked issues and PRs when working on specific features :  </strong>Add the issue URL before you start, and Copilot&#8217;s suggestions become grounded in the actual problem statement.</p></li></ol><div class="pullquote"><p>Don&#8217;t add generated files, migration history, large data fixtures, or files you&#8217;re actively refactoring. More context is not always better. Focused, relevant context produces better results than a dump of everything.</p></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!V0gv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!V0gv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!V0gv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!V0gv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!V0gv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!V0gv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1125168,&quot;alt&quot;:&quot;Infographic showing the four components of a Python Copilot Space and how they connect to produce grounded responses&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/198866997?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Infographic showing the four components of a Python Copilot Space and how they connect to produce grounded responses" title="Infographic showing the four components of a Python Copilot Space and how they connect to produce grounded responses" srcset="https://substackcdn.com/image/fetch/$s_!V0gv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!V0gv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!V0gv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!V0gv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F892df1a4-f88c-42f0-835e-4cf0efa3cfa1_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>Writing Instructions That Actually Work</h3><p>The Instructions panel is the highest-leverage part of the setup. Generic instructions produce generic results. Specific instructions produce code that fits your project.</p><p>Here&#8217;s a concrete example for a FastAPI + Pydantic v2 + pytest stack:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;03953b3d-c8ae-463d-9f10-357ec0c73f85&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Architecture:
- All request handlers must subclass BaseHandler from app/handlers/base.py
- Use FastAPI&#8217;s Depends() for all service injection &#8212; never instantiate services directly in handlers
- Router prefixes follow the pattern in app/routers/__init__.py

Data models:
- Use Pydantic v2 models for all request and response types
- Response models live in app/models/responses.py, request validation in app/models/requests.py
- Never return a raw dict from an endpoint &#8212; always use a response model

Testing:
- Use pytest. Never use unittest.TestCase.
- Fixtures are defined in tests/conftest.py &#8212; reuse existing fixtures before creating new ones
- Test files mirror the app structure: tests/handlers/ for handler tests

Code standards:
- All functions require type annotations, including return types
- snake_case for functions and variables, PascalCase for classes
- Cite which existing file your pattern is based on
- Check if an existing model covers the need before creating a new one</code></pre></div><p>A few things worth explaining: the Pydantic v2 instruction is specifically necessary because Pydantic v1 and v2 have incompatible APIs. Copilot&#8217;s training data includes both. Without an explicit instruction, it may default to v1 syntax, particularly for older patterns. The same applies to pytest vs. unittest &#8212; both are common in Python codebases, and Copilot picks based on what&#8217;s most prevalent in its training, not what your project uses.</p><p>Every line in that instruction block removes a class of errors from the output. Start with whatever your team repeats most in PR reviews. Those are the conventions Copilot needs to know.</p><h3>Using Spaces in Your IDE</h3><p>Since the September 2025 GA release, Spaces are accessible directly from VS Code, JetBrains, and any IDE with the GitHub Copilot extension and MCP server support. No browser switching.</p><p>To enable it, configure the remote GitHub MCP server with the `copilot_spaces` toolset:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;ca3f1995-f847-4766-8896-106f3be27cab&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">{
  &#8220;servers&#8221;: {
    &#8220;github&#8221;: {
      &#8220;type&#8221;: &#8220;http&#8221;,
      &#8220;url&#8221;: &#8220;https://api.githubcopilot.com/mcp/&#8221;,
      &#8220;headers&#8221;: {
        &#8220;X-MCP-Toolsets&#8221;: &#8220;default,copilot_spaces&#8221;
      }
    }
  }
}</code></pre></div><p>Then open Copilot Chat in <strong>Agent mode</strong> (required &#8212; Spaces are not available in regular chat mode), and reference your Space by name:</p><p><code>Using the Copilot space &#8216;FastAPI Pipeline Service&#8217;, add a new batch processing endpoint following the same pattern as the existing batch_ingest handler.</code></p><p>Copilot retrieves your Space context and responds with knowledge of your project&#8217;s architecture.</p><p><strong>One important limitation to know upfront:</strong> <br>When using Spaces from your IDE, repository search does not run. You have access to the Instructions panel and all directly-attached files, but not the live search of the connected repository. This makes the file selection decisions from the previous section more important in IDE mode than in the GitHub web interface. Put your most critical patterns in directly-attached files rather than relying on repository search to find them.</p><p>This is a current limitation of the IDE integration. Check GitHub&#8217;s documentation for updates as the feature evolves.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lQc0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lQc0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!lQc0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!lQc0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!lQc0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lQc0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1054311,&quot;alt&quot;:&quot;Step-by-step flow diagram of IDE to Copilot Spaces integration showing the repo search limitation in the IDE path&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/198866997?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Step-by-step flow diagram of IDE to Copilot Spaces integration showing the repo search limitation in the IDE path" title="Step-by-step flow diagram of IDE to Copilot Spaces integration showing the repo search limitation in the IDE path" srcset="https://substackcdn.com/image/fetch/$s_!lQc0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!lQc0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!lQc0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!lQc0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcdb915-0b25-48db-a38f-0ee53649f269_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>Team Workflows: Scaling What Senior Engineers Know</h3><p>A well-built Space is useful for one engineer. Shared across a team, it becomes something more structural.</p><p>Every Python team has a few engineers who carry most of the architectural knowledge. They know which patterns to use, which to avoid, and why decisions got made the way they did. When someone needs to add a new feature or debug an unfamiliar service, they ask one of these people. That knowledge transfer is valuable and fragile &#8212; it doesn&#8217;t scale, it&#8217;s synchronous, and it leaves when people do.</p><p>A shared Space puts that knowledge into a durable form. The architecture docs, the convention decisions, the active issues &#8212; curated once, available to everyone.</p><p><strong>For onboarding:  </strong>New engineers start with a conversation partner that knows the codebase patterns from day one. They can ask &#8220;how does auth work here?&#8221; or &#8220;what&#8217;s the right pattern for adding a new endpoint?&#8221; and get grounded answers rather than a two-day walkthrough from a senior engineer.</p><p><strong>For consistency:</strong> On a team of five engineers, each person has a slightly different internal model of the project&#8217;s conventions. Shared Space instructions codify the agreed-upon patterns. When a convention changes &#8212; switching dependency managers, upgrading a major library &#8212; the instructions update, and everyone&#8217;s Copilot context shifts accordingly.</p><p><strong>For feature work:</strong> Before starting implementation, link the relevant issue and design doc to the Space. Ask Copilot for an implementation plan. Approve it, then trigger the coding agent to draft a pull request. The coding agent works from the same project context, so its first draft is closer to what the PR reviewer will accept.</p><p>In GitHub organizations, Spaces can be shared with org members at admin, editor, or viewer access levels. For team Spaces, editor access for the engineers maintaining the conventions, viewer access for everyone else, is a reasonable default.</p><div><hr></div><h3>Where Spaces Fall Short</h3><p>A Space improves Copilot&#8217;s suggestions. It doesn&#8217;t make them reliable enough to skip review.</p><p><strong>IDE repository search is not supported:</strong> Already covered, but the practical implication: for IDE users, a Space&#8217;s value depends almost entirely on what you&#8217;ve attached directly. If your most important patterns live only in the repository connection, they won&#8217;t be available in IDE mode.</p><p><strong>Instructions need maintenance:</strong> Stale instructions actively cause problems. If your team moved from Pydantic v1 to v2 but the instructions still reference v1 patterns, Copilot follows the instructions &#8212; toward the code you&#8217;re trying to move away from. Assign ownership. Review the instructions whenever a major convention changes.</p><p><strong>Large repositories may produce noisy results:</strong> For Python monorepos with multiple packages, the repository search may retrieve content from sibling packages rather than just the one you&#8217;re working in. Scope Spaces per service or module rather than covering the entire monorepo.</p><p><strong>Suggestions still require verification:</strong> Run your type checker, your linter, your tests. A Space-grounded suggestion is a well-informed first draft, not verified code. The gap between the two matters.</p><h3>Starting Small</h3><p>The most useful first step is also the simplest. Pick one service. Connect its repository. Attach three files &#8212; your base class, your main models, and your conftest. Write ten lines of instructions covering the patterns your team most frequently corrects in PRs. Ask Copilot to generate one new component following the existing patterns. Compare the result to what you&#8217;d get without the Space.</p><p>That comparison tells you what the setup is worth for your particular project.</p><p>From there, add the issue links when you&#8217;re working on something specific. Update the instructions when the team adopts a new convention. Share the Space when it&#8217;s stable enough to be useful to others.</p><p>The goal isn&#8217;t to give Copilot everything. It&#8217;s to give it the right things: the patterns that define how your project works, specific enough that its output starts from the right place.</p><div><hr></div><p></p><p>REFERENCES : </p><ul><li><p><a href="https://github.blog/changelog/2025-09-24-copilot-spaces-is-now-generally-available/">GitHub Copilot Spaces GA announcement (September 24, 2025)</a></p></li><li><p><a href="https://docs.github.com/en/copilot/concepts/context/spaces">About GitHub Copilot Spaces (official docs)</a></p></li><li><p><a href="https://docs.github.com/en/copilot/how-tos/provide-context/use-copilot-spaces/create-copilot-spaces">Creating GitHub Copilot Spaces</a></p></li><li><p><a href="https://docs.github.com/en/copilot/how-tos/provide-context/use-copilot-spaces/use-copilot-spaces">Using GitHub Copilot Spaces</a></p></li><li><p><a href="https://docs.github.com/en/copilot/tutorials/speed-up-development-work">Speeding up development work with Copilot Spaces</a></p></li><li><p><a href="https://github.blog/ai-and-ml/github-copilot/how-to-use-github-copilot-spaces-to-debug-issues-faster/">How to use Copilot Spaces to debug issues faster</a></p></li><li><p><a href="https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp/set-up-the-github-mcp-server">Setting up the GitHub MCP Server</a></p></li></ul><div><hr></div><h3>TL;DR</h3><p>GitHub Copilot Spaces, generally available since September 2025, lets you curate the context Copilot uses when answering questions &#8212; your files, architecture docs, open issues, and behavioral instructions &#8212; so its suggestions fit your project&#8217;s specific patterns rather than generic Python conventions. </p><p>For Python teams, the highest-value setup combines a connected repository, a small set of directly attached files (your base class, Pydantic models, pytest conftest), and a specific Instructions panel that enforces your conventions. Spaces also integrate with VS Code and JetBrains via the GitHub MCP server, though the IDE integration currently does not support repository search &#8212; so the files you attach directly matter more in IDE mode. Shared Spaces turn individual context into a team knowledge system, reducing onboarding time and PR correction cycles.</p>]]></content:encoded></item><item><title><![CDATA[Floci: A Practical Guide To Local AWS Emulation]]></title><description><![CDATA[Floci helps teams test AWS-backed code locally and in CI, it works best as one layer in a broader cloud testing strategy.]]></description><link>https://ganeshkedari.substack.com/p/floci-a-practical-guide-to-local</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/floci-a-practical-guide-to-local</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Thu, 14 May 2026 16:29:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2PFl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><h3>Floci: A Practical Guide To Local AWS Emulation</h3><p>The delay usually starts with something small.</p><p>A developer changes a queue handler, runs the unit tests, and everything looks fine. Then the real question appears: will this still work when the S3 event, SQS message, Lambda handler, DynamoDB status update, and IAM permissions all meet each other? If the first useful answer requires a shared AWS dev account, stale test resources, and a cleanup script nobody fully trusts, the team is already paying a feedback-loop tax.</p><p>Mocks help, but mocks are often too polite. Real AWS is accurate, but it is not the right place to discover every malformed event and missing fixture.</p><p>That is where Floci becomes interesting.</p><p>Floci is an open-source local AWS emulator. You run it locally, point AWS CLI or SDK calls to <code>http://localhost:4566</code>, and test AWS-backed workflows before they reach a real cloud account. The current Floci documentation lists 45 emulated AWS services, and the project positions itself around a familiar local endpoint workflow, MIT licensing, no auth token, no feature gates, and Docker-backed execution for some complex services.</p><p>The practical point is not that Floci replaces AWS.</p><p>It does not.</p><p>The useful point is that Floci can answer more AWS-boundary questions earlier. Used well, it reduces waiting. Used carelessly, it can make local success look more complete than it is.</p><h3>What Floci Is And Where It Fits</h3><p>Floci is a local AWS emulator for development and CI. In plain English, it gives your application local endpoints that behave like AWS service endpoints, so your code can call S3-like, SQS-like, Lambda-like, DynamoDB-like, and other AWS-style APIs without using a real AWS account for every development loop.</p><p>That port will feel familiar to anyone who has used LocalStack-style workflows. The idea is not to rewrite application logic around custom mocks. The idea is to keep normal AWS SDK or CLI calls, then change the endpoint for local development and CI.</p><h3>Why Local AWS Emulation Matters</h3><p>Cloud development gets slow when every integration question needs a real cloud environment.</p><p>Take a normal AWS-backed feature. A file lands in S3. A message moves through SQS. A Lambda function validates the payload. DynamoDB stores processing state. EventBridge publishes an outcome. The code may be small, but the behavior depends on service boundaries.</p><p>If the team tests only with mocks, it may miss event shapes, endpoint configuration, serialization issues, retry behavior, and state transitions. If the team tests every pull request only in AWS, it inherits account setup, permissions, cleanup, cost controls, networking, latency, and shared-environment noise.</p><p>Local emulation sits in the middle. It gives developers and CI a place to test cloud-adjacent behavior without treating AWS as the first debugger.</p><p>That matters because feedback changes behavior. When useful tests are easy to run, developers run them more often. When reproducing a failure requires a shared account and manual cleanup, people wait longer, test less, and push uncertainty downstream.</p><h3>The Mental Model: One Layer In The Testing Stack</h3><p>The cleanest way to think about Floci is as one layer in a larger testing stack.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6eEj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6eEj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!6eEj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!6eEj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!6eEj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6eEj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7b842758-0783-46f6-9364-a993098289f9_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1321034,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/197680381?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6eEj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!6eEj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!6eEj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!6eEj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b842758-0783-46f6-9364-a993098289f9_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>This model prevents two common mistakes.</p><p>The first mistake is overusing real AWS. If every small pull request needs a cloud environment just to test a basic service path, the team will move slowly and eventually skip tests.</p><p>The second mistake is overtrusting the emulator. A local pass does not prove production IAM, VPC networking, throttling, quotas, region-specific behavior, Lambda concurrency, or managed-service edge cases.</p><p>Floci is strongest in the middle. It helps with service-boundary tests that are too real for unit mocks, but too frequent to push into AWS every time.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2PFl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2PFl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png 424w, https://substackcdn.com/image/fetch/$s_!2PFl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png 848w, https://substackcdn.com/image/fetch/$s_!2PFl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png 1272w, https://substackcdn.com/image/fetch/$s_!2PFl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2PFl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png" width="1456" height="799" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:799,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1081292,&quot;alt&quot;:&quot;Diagram showing Floci as a local AWS emulator between developer tests, CI tests, and final real AWS validation.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/197680381?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Diagram showing Floci as a local AWS emulator between developer tests, CI tests, and final real AWS validation." title="Diagram showing Floci as a local AWS emulator between developer tests, CI tests, and final real AWS validation." srcset="https://substackcdn.com/image/fetch/$s_!2PFl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png 424w, https://substackcdn.com/image/fetch/$s_!2PFl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png 848w, https://substackcdn.com/image/fetch/$s_!2PFl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png 1272w, https://substackcdn.com/image/fetch/$s_!2PFl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fb251ec-0a0e-4603-80ef-ccaff2f5767d_1693x929.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h4>Getting Started With The Current Floci Setup</h4><p>The basic Docker Compose setup from the current docs is intentionally small:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;yaml&quot;,&quot;nodeId&quot;:&quot;721db897-f413-4720-a6db-8f1b1de24808&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-yaml">services:

  floci:

    image: floci/floci:latest

    ports:

      - &#8220;4566:4566&#8221;

    volumes:

      - ./data:/app/data</code></pre></div><p>Start it:</p><p></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;029d647f-0c58-42b0-86d9-5c485e95c43a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">docker compose up -d</code></pre></div><p></p><p>Then point the AWS CLI to the local endpoint:</p><p></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;9733c129-a73f-4933-844e-9f98d5d2b416&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">aws --endpoint-url http://localhost:4566 s3 mb s3://local-ingestion</code></pre></div><p></p><p>For application code, keep the same AWS SDK pattern and configure the local endpoint for development or CI.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;cc14980d-830b-4bc5-a0cc-78b74b8565a1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import boto3

s3 = boto3.client(

    &#8220;s3&#8221;,

    endpoint_url=&#8221;http://localhost:4566&#8221;,

    aws_access_key_id=&#8221;test&#8221;,

    aws_secret_access_key=&#8221;test&#8221;,

    region_name=&#8221;us-east-1&#8221;,

)

s3.create_bucket(Bucket=&#8221;local-ingestion&#8221;)

s3.put_object(

    Bucket=&#8221;local-ingestion&#8221;,

    Key=&#8221;orders/sample.json&#8221;,

    Body=b&#8217;{&#8221;order_id&#8221;: &#8220;1001&#8221;, &#8220;status&#8221;: &#8220;received&#8221;}&#8217;,

)</code></pre></div><p>That example is small on purpose. Engineers scan setup sections. The important thing is the pattern: keep normal AWS client usage, configure a local endpoint, create local resources, then run tests against that boundary.</p><p>There are a few updated setup details to keep in mind.</p><ol><li><p>Use floci/floci, not the older image name</p></li><li><p>The repository documents floci/floci:latest-compat for cases where init scripts need AWS CLI or boto3 inside the image.</p></li><li><p>Docker-backed services may require access to the Docker socket, especially when Floci needs to run real containers for service behavior.</p></li></ol><p>That third point is where local convenience becomes team operations. Docker socket access can matter for CI security. Runner resources matter for reliability. Image tags matter for repeatability. Cleanup matters because flaky local state can waste just as much time as flaky cloud state.</p><h4>A Practical Scenario: The Schema Mismatch That Arrived Too Late</h4><p>Imagine a data platform team building an event-driven ingestion workflow.</p><p>The path is common:</p><ol><li><p>A partner file lands in S3.</p></li><li><p>SQS carries the notification.</p></li><li><p>Lambda validates the file.</p></li><li><p>DynamoDB stores processing status.</p></li><li><p>EventBridge publishes the outcome.</p></li></ol><p>The first version works. Then a partner sends a file with <code>customerId</code> while the validator expects <code>customer_id</code>.</p><p>The failure is not exotic. It is the kind of mismatch that appears in real enterprise systems all the time. The problem is that the unit test uses a clean mock payload, so it passes. The shared AWS dev account eventually catches the issue, but only after the queue has stale messages, the table has old status records, and someone has to replay the fixture by hand.</p><p>With Floci, this can become a local integration test.</p><p>The developer starts Floci, creates the local bucket and table, uploads the bad fixture, runs the validation path, and asserts that the file is rejected before any accepted status is written. The test does not need a shared cloud account. It does not need yesterday&#8217;s cleanup script. It gives a useful answer while the context is still fresh.</p><p>That is the moment Floci earns attention. It moves a realistic AWS-boundary mistake earlier without pretending the local environment is production.</p><h4>What Floci Gets Right, And Where It Gets Complicated</h4><p>Floci gets the developer workflow right by staying close to normal AWS client behavior. Endpoint configuration is easier to reason about than a custom mock layer that changes how the application is structured. If the team already understands LocalStack-style development, port 4566 is not a new mental model.</p><p>Floci also have broad service coverage. That matters because real AWS workflows often cross service boundaries. A local emulator that only covers one service can help a narrow test, but it cannot exercise an event-driven path across storage, messaging, compute, and state.</p><p>The part worth watching is Docker-backed execution for complex services. Some behaviors are hard to fake well. Databases, Redis-compatible systems, Kafka-compatible brokers, build containers, Lambda runtimes, and search systems often need more than simple in-memory responses. Floci&#8217;s Docker-backed direction can make certain tests more meaningful.</p><p>That said, service count is not the same as service depth. If your workflow depends on a specific IAM policy edge case, S3 event behavior, Lambda event-source mapping, Step Functions branch, DynamoDB stream pattern, or region-specific behavior, check that path directly.</p><p>Project-reported performance numbers should be handled the same way. The Floci site reports strong startup, memory, image-size, and compatibility numbers. Those are useful reasons to evaluate the tool. They are not a guarantee that your CI runner, service mix, and test suite will see the same results.</p><p>The metric that matters is practical: does Floci reduce feedback time while catching real defects and keeping release confidence intact?</p><p>[Insert Image 2: Placeholder - Layered AWS Testing Strategy With Floci]</p><h4>Floci Compared With Other Options</h4><p>Most teams do not need one perfect answer. They need the right mix.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tIxK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tIxK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png 424w, https://substackcdn.com/image/fetch/$s_!tIxK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png 848w, https://substackcdn.com/image/fetch/$s_!tIxK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png 1272w, https://substackcdn.com/image/fetch/$s_!tIxK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tIxK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png" width="1456" height="846" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:846,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1032935,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/197680381?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tIxK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png 424w, https://substackcdn.com/image/fetch/$s_!tIxK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png 848w, https://substackcdn.com/image/fetch/$s_!tIxK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png 1272w, https://substackcdn.com/image/fetch/$s_!tIxK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5d53992-c301-49e1-a78a-6c4db25919da_1645x956.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>AWS DynamoDB Local is a good example of a service-specific local tool. AWS documents it as a way to develop and test without accessing the DynamoDB web service, then remove the local endpoint when deploying to production. That is useful, but it solves a narrower problem than a multi-service emulator.</p><p><a href="https://www.localstack.cloud/">LocalStack </a>is the established name in this category and remains an important comparison point. Current LocalStack installation docs show Docker as a requirement and describe auth token setup before starting. Floci&#8217;s no-auth-token positioning is relevant here, especially for open-source-friendly and CI-heavy workflows, but the decision should stay technical: service fit, team workflow, reliability, and support expectations.</p><p>Real AWS remains the highest-fidelity environment. It is also the most expensive place to discover every small integration mistake. The better pattern is not replacement. It is layering.</p><h4>When Floci Earns Its Place, And When To Limit It</h4><p>Floci earns its place when developers need repeatable AWS-like feedback before code reaches a shared cloud account. It is a good fit for event-driven workflows, backend services that call AWS APIs, data ingestion pipelines, local reproduction of cloud-adjacent bugs, and CI jobs that should catch service-boundary mistakes early.</p><p>It is the wrong final gate when the risk depends on exact AWS behavior. IAM correctness, VPC routing, DNS behavior, service quotas, throttling, regional differences, compliance controls, deployment wiring, and managed-service edge cases still belong in real AWS validation.</p><p>That framing matters. Floci is not a shortcut around production confidence. It is a way to arrive at real AWS with fewer avoidable mistakes.</p><h4>Practical Gut-Check Before Adopting Floci</h4><p>Before committing to this, make sure you can answer yes to these:</p><ul><li><p>Do we know which AWS operations our local tests need?</p></li><li><p>Can Floci cover those operations well enough for useful feedback?</p></li><li><p>Can our developer machines and CI runners support the required Docker setup?</p></li><li><p>Are image tags pinned for repeatable CI runs?</p></li><li><p>Can test resources and local state be reset cleanly?</p></li><li><p>Do endpoint settings clearly separate local Floci from real AWS accounts?</p></li><li><p>Do we still run real AWS smoke tests for critical paths?</p></li></ul><p>If those answers are unclear, Floci may still be worth exploring, but the team should treat the first step as an evaluation, not an adoption decision.</p><h4>My Take</h4><p>Floci is worth watching because it focuses on a real engineering problem: AWS feedback often arrives too late.</p><p>The best teams will not use it to avoid AWS. They will use it to stop asking AWS questions that can be answered earlier, locally, and repeatedly. That distinction is important. One approach cuts corners. The other improves the development system.</p><p>For me, the practical test is straightforward: if Floci helps a team catch AWS-boundary mistakes earlier without weakening release confidence, it deserves a serious look.</p><p>The laptop does not need to become the cloud. It just needs to stop waiting for the cloud to answer every small question.</p><p>Suggested References And Further Reading</p><p>- Floci official documentation: <a href="https://floci.io/floci/">https://floci.io/floci/</a></p><p>- Floci GitHub repository: <a href="https://github.com/floci-io/floci">https://github.com/floci-io/floci</a></p><p>- LocalStack installation documentation: <a href="https://docs.localstack.cloud/aws/getting-started/installation/">https://docs.localstack.cloud/aws/getting-started/installation/</a></p><p>- AWS DynamoDB Local documentation: <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html">https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html</a></p>]]></content:encoded></item><item><title><![CDATA[Stop Fighting PDFs. Start Building Better RAG Pipelines with RAGFlow]]></title><description><![CDATA[RAGFlow puts document understanding before retrieval, which is exactly where many enterprise RAG systems fail.]]></description><link>https://ganeshkedari.substack.com/p/stop-fighting-pdfs-start-building</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/stop-fighting-pdfs-start-building</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Wed, 13 May 2026 12:48:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6omM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The RAG Demo Worked. Production Did Not.</h2><p>Every RAG system looks good with three clean PDFs and a happy-path question.</p><p>Then production arrives with scanned procedures, two-column layouts, tables that continue across pages, headers, footers, footnotes, version blocks, and a few documents that were clearly exported during a coffee break.</p><p>The model gets blamed first. It usually should not.</p><p>Many RAG systems fail before the LLM sees anything useful. The document was parsed badly. The table was split in half. The section header disappeared. The chunk contains a page number, half a sentence, and a lonely row from a table that has lost its context.</p><p>The LLM cannot reason over broken context. It can only sound confident while doing its best with damaged input.</p><p>RAGFlow is interesting because it focuses on that unglamorous middle layer. It is an open-source Retrieval-Augmented Generation engine from InfiniFlow that treats document understanding as a core part of the architecture, not a preprocessing chore.</p><h2>The Mental Model</h2><p>Think of conventional RAG as a simple pipeline:</p><div class="callout-block" data-callout="true"><p>Load document &gt; Split text &gt; Embed chunks &gt; Retrieve chunks &gt; Ask the LLM</p></div><p>That works for clean text. It struggles with real enterprise documents.</p><p>RAGFlow adds more discipline before retrieval. It parses documents, preserves structure where possible, applies chunking strategies, supports hybrid retrieval, and lets humans inspect chunks before the system answers questions.</p><p>The flow looks like this:</p><ol><li><p>Ingest documents such as PDFs, Word files, slides, images, web pages, and structured files.</p></li><li><p>Parse the content with attention to layout and document structure.</p></li><li><p>Chunk the document using a method that fits the content type.</p></li><li><p>Retrieve with a mix of semantic and keyword-style matching.</p></li><li><p>Generate answers with source references.</p></li></ol><p>The most underrated part is chunk inspection. Before the first user query, you can check what the system actually extracted. That is where trust starts.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6omM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6omM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png 424w, https://substackcdn.com/image/fetch/$s_!6omM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png 848w, https://substackcdn.com/image/fetch/$s_!6omM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png 1272w, https://substackcdn.com/image/fetch/$s_!6omM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6omM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png" width="1456" height="799" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:799,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1175715,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/195477171?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6omM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png 424w, https://substackcdn.com/image/fetch/$s_!6omM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png 848w, https://substackcdn.com/image/fetch/$s_!6omM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png 1272w, https://substackcdn.com/image/fetch/$s_!6omM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5704059-b29b-4ec5-899f-0fec7fa3cc0d_1693x929.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Why Naive Chunking Fails</h2><p>Chunking sounds simple until you meet a manufacturing SOP.</p><p>A procedure might have numbered steps, nested warnings, tables, acceptance criteria, and references to attachments. If you split that document every 800 characters, you may separate the warning from the step it controls. If you split by paragraph, you may break a table. If you use a large chunk, retrieval may return too much noise.</p><p>The problem is not chunk size. The problem is meaning.</p><p>Enterprise documents carry structure. Section hierarchy matters. Tables matter. Page layout matters. Captions matter. Version blocks matter. A RAG pipeline that ignores structure forces the retriever to search damaged pieces.</p><p>This is why teams spend weeks tuning embedding models and prompts when the real issue is upstream. The chunk is wrong. Everything after that is compensation.</p><div class="pullquote"><p>RAGFlow&#8217;s pitch is practical: improve the context before asking the model to generate an answer.</p></div><h3>A Practical Walkthrough</h3><p>Let us use a manufacturing SOP knowledge base as the example.</p><p>The goal is to let operators ask questions such as, &#8220;What inspection steps apply before restarting Line 3 after a temperature deviation?&#8221; The answer should cite the exact SOP section and version. The QA team should be able to trace the answer back to the source.</p><h4>Step 1: Audit The Documents First</h4><p>Before installing anything, inspect a representative sample.</p><p>Pick five to ten documents that reflect the real library: clean PDFs, scanned PDFs, Word documents, tables, procedures, and old templates. Note which documents are digital-native and which need OCR.</p><p>This is not paperwork. It is architecture.</p><p>A document library with consistent templates is a different problem from a folder full of scans, exports, and legacy forms.</p><h4>Step 2: Start With The Official Docker Setup</h4><p>For a local evaluation, use the official RAGFlow repository and Docker setup. Keep this as an evaluation environment, not a production deployment.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;d1ef26d7-61d7-4f42-8315-1e6b831a2242&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">git clone https://github.com/infiniflow/ragflow.git

cd ragflow/docker

docker compose -f docker-compose.yml up -d</code></pre></div><p>The official quickstart includes operating system and Docker prerequisites, plus checks for the service startup logs. Follow those details from the RAGFlow documentation because they can change as the project evolves.</p><h4>Step 3: Configure Models Deliberately</h4><p>RAGFlow needs LLM and embedding configuration.</p><p>For an enterprise evaluation, decide early whether the prototype can call external model APIs or must stay inside a controlled environment. That decision affects cost, latency, data handling, and security review.</p><p>Do not leave model choice as the last checkbox. It influences retrieval quality, answer format, and deployment boundaries.</p><h4>Step 4: Upload A Small Document Set</h4><p>Upload the representative documents first. Resist the urge to bulk ingest the entire library.</p><p>The first job is not coverage. The first job is learning how RAGFlow parses your documents.</p><p>Use a procedural document, a table-heavy document, and one scanned file if those exist in the real estate. If RAGFlow handles those well, you can scale ingestion with more confidence.</p><h4>Step 5: Inspect The Chunks</h4><p>This is the part teams skip.</p><p>Open the chunk view and inspect the output. Check whether section titles stayed attached to the content. Check whether tables remained readable. Check whether page headers and footers polluted the chunks. Check whether the chunk boundary makes sense for the questions users will ask.</p><p>If the chunk is bad, do not tune the prompt yet. Fix parsing and chunking first.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PqPd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PqPd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png 424w, https://substackcdn.com/image/fetch/$s_!PqPd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png 848w, https://substackcdn.com/image/fetch/$s_!PqPd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png 1272w, https://substackcdn.com/image/fetch/$s_!PqPd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PqPd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png" width="1456" height="799" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:799,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1460759,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/195477171?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PqPd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png 424w, https://substackcdn.com/image/fetch/$s_!PqPd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png 848w, https://substackcdn.com/image/fetch/$s_!PqPd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png 1272w, https://substackcdn.com/image/fetch/$s_!PqPd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e04c59-05eb-48a8-90c0-46c758b753af_1692x929.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h4>Step 6: Test Questions Before Building A UI</h4><p>Ask questions that reflect real work:</p><ul><li><p>&#8220;What steps are required before restarting the line?&#8221;</p></li><li><p>&#8220;Which section covers escalation after a failed inspection?&#8221;</p></li><li><p> &#8220;What does the SOP say about documenting deviations?&#8221;</p></li><li><p>&#8220;Where is the acceptance threshold for this process?&#8221;</p></li></ul><p>Good answers are not enough. Look at the retrieved sources. If the answer is correct but the citation points to a weak chunk, the system is not ready.</p><p>Expected behavior for the SOP prototype:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;f61b5be4-b7f6-45e0-b096-483365b45b57&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">The system retrieves the relevant SOP section, preserves the procedure context, cites the source document and section, and avoids answering when the source text does not support the claim.</code></pre></div><p>That last part matters. A knowledge system that refuses correctly is more useful than one that answers everything.</p><h3>Where RAGFlow Works Well</h3><p>RAGFlow fits document-heavy environments.</p><p>Manufacturing SOPs, regulatory protocols, product manuals, policy documents, inspection guides, contracts, training manuals, and technical documentation are good candidates. These documents are structured, important, and usually painful to search.</p><p>It also fits teams that need auditability. If QA, compliance, legal, or operations teams need to see why an answer was generated, chunk inspection and source references are not nice extras. They are part of the control model.</p><p>Hybrid retrieval is another strength. Dense vector search helps with meaning. Sparse keyword-style search helps with exact terms such as product codes, procedure IDs, clause numbers, and regulation references. Enterprise queries often need both.</p><h4>What To Watch For</h4><p>RAGFlow does not remove the need for document governance.</p><p>If the source library contains outdated files, duplicate SOPs, missing version metadata, and conflicting procedures, RAGFlow will expose the problem. It will not solve ownership for you.</p><p>Teams also underestimate chunk review. The tool gives you visibility, but a person still has to inspect the output and decide whether it is good enough. This is slow compared with just clicking &#8220;ingest all.&#8221; It is also the difference between a demo and a system people trust.</p><p>At scale, ingestion and operations need planning. Hundreds of daily document updates, multiple business units, access controls, tenant boundaries, and automated publishing workflows require more than a web UI evaluation. The API and deployment architecture need proper design.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QHEI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QHEI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png 424w, https://substackcdn.com/image/fetch/$s_!QHEI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png 848w, https://substackcdn.com/image/fetch/$s_!QHEI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png 1272w, https://substackcdn.com/image/fetch/$s_!QHEI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QHEI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png" width="1456" height="857" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:857,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1037807,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/195477171?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QHEI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png 424w, https://substackcdn.com/image/fetch/$s_!QHEI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png 848w, https://substackcdn.com/image/fetch/$s_!QHEI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png 1272w, https://substackcdn.com/image/fetch/$s_!QHEI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd75a5cf-643b-455a-ba53-012624e9e2e3_1635x962.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="pullquote"><p>Choose RAGFlow when document quality is the core problem. Choose a simple vector pipeline when the content is clean and the use case is lightweight. Choose a custom framework when the organization needs full control over every part of the retrieval stack.</p></div><h3>When To Use It</h3><p>Use RAGFlow when your documents are messy, structured, regulated, or operationally important. It is especially useful when teams need to inspect what the system read, trace answers back to source chunks, and improve retrieval before tuning prompts.</p><p>Avoid it when the content is small, clean, and already structured. A simple search index or a lightweight RAG pipeline may be enough. Also avoid it if nobody on the team will review chunks. The tool can expose quality. It cannot care on your behalf.</p><h3>My Take</h3><p>RAGFlow is not exciting because it is another RAG tool.</p><p>It is useful because it focuses on the part of RAG that teams keep underestimating: document understanding.</p><p>In BI to AI modernization work, this is the difference between &#8220;we connected documents to a model&#8221; and &#8220;we built a knowledge layer people can inspect and trust.&#8221; That second version is harder. It is also the one that survives production review.</p><p>Start with a small document set. Inspect chunks. Tune retrieval. Make citations visible. Then expand.</p><p>The best RAG systems do not just answer questions. They show their work.</p><p>Suggested References</p><ul><li><p>RAGFlow official site: https://ragflow.io/</p></li><li><p>RAGFlow GitHub repository: https://github.com/infiniflow/ragflow</p></li><li><p>RAGFlow official docs repository: https://github.com/infiniflow/ragflow-docs</p></li><li><p>RAGFlow Python API reference: https://github.com/infiniflow/ragflow/blob/main/docs/references/python_api_reference.md</p></li><li><p>RAGFlow Docker image page: https://hub.docker.com/r/infiniflow/ragflow</p></li><li><p>Academic overview of dense retrieval: https://arxiv.org/abs/2004.04906</p></li><li><p>BM25 background, Robertson and Zaragoza: https://www.nowpublishers.com/article/Details/INR-019</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Getting Started with JSONL: A Practical Guide for Data Engineers and AI Practitioners]]></title><description><![CDATA[JSONL lets you process massive datasets line by line, no memory explosions, no parsing headaches. Here&#8217;s how to start using it.]]></description><link>https://ganeshkedari.substack.com/p/getting-started-with-jsonl-a-practical</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/getting-started-with-jsonl-a-practical</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Thu, 12 Mar 2026 06:45:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!LlNu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last week, a colleague pinged me about a pipeline that kept crashing. The culprit? A 12GB JSON file that their ETL job tried to load entirely into memory. The fix took five minutes. Convert the file to JSONL, read it line by line, move on with life.</p><p>This keeps happening. Teams wrestle with bloated JSON arrays or fight CSV&#8217;s quirks around nested data, and nobody stops to ask whether a simpler format exists. It does. It&#8217;s called JSONL, and it&#8217;s been quietly powering AI training pipelines, log ingestion systems, and streaming architectures for years.</p><p>JSONL won&#8217;t solve every problem. But for a surprising number of data engineering and AI workflows, it&#8217;s the most practical choice you&#8217;re not using yet.</p><h1>What is JSONL?</h1><p>JSONL stands for JSON Lines. The idea is almost embarrassingly simple: take a file, put one valid JSON object on each line, and call it a day.</p><p>That&#8217;s it. No wrapper array. No commas between records. No closing bracket at the end. Each line is self-contained and independently parseable.</p><p>Here&#8217;s what a JSONL file looks like:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;53d1fc34-81bd-402b-9c94-44acf49bc546&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">{&#8220;id&#8221;: 1, &#8220;name&#8221;: &#8220;Alice&#8221;, &#8220;role&#8221;: &#8220;Engineer&#8221;}
{&#8220;id&#8221;: 2, &#8220;name&#8221;: &#8220;Bob&#8221;, &#8220;role&#8221;: &#8220;Data Scientist&#8221;}
{&#8220;id&#8221;: 3, &#8220;name&#8221;: &#8220;Ganesh&#8221;, &#8220;role&#8221;: &#8220;Tech Lead&#8221;}</code></pre></div><p>Compare that with standard JSON, where you&#8217;d wrap everything in an array, add commas, and force any parser to read the entire structure before it can hand you a single record. With JSONL, your code reads one line, parses one object, processes it, and moves to the next. Memory stays flat regardless of file size.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LlNu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LlNu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!LlNu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!LlNu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!LlNu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LlNu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2644992,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/190697818?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LlNu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!LlNu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!LlNu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!LlNu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43056a4d-c76a-4e01-abbd-d18ef2fd4576_2752x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The formal spec lives at <a href="https://jsonlines.org/">jsonlines.org</a>, but honestly, the format is so intuitive that most engineers start using it before they even know it has a name.</p><h2>Why JSONL Matters Right Now</h2><p>Three trends have pushed JSONL from obscure to essential.</p><ul><li><p><strong>AI and LLM training pipelines: </strong><br>If you&#8217;ve fine-tuned a model using OpenAI&#8217;s API, you&#8217;ve already used JSONL. Training data for GPT-style models, embeddings, and classification tasks ships as JSONL because each record is an independent training example. Line-delimited means you can shuffle, sample, or split files with basic Unix tools. Try doing that with a nested JSON array.</p></li><li><p><strong>Log and Event Streaming:</strong> <br>Server logs, application events, clickstreams: they&#8217;re all naturally line-oriented. JSONL fits like a glove. Each event is one line. You can tail a file, grep for a field, or pipe it into Kafka without a custom parser.</p></li><li><p><strong>Memory-Safe Data Ingestion :</strong> <br>At a client project, we had 50GB of semi-structured product data from a supply chain system. The original pipeline loaded everything into a pandas DataFrame and promptly ran out of memory. Converting to JSONL and streaming line by line through Spark let us build embeddings on a modest cluster without a single OOM error.</p></li></ul><blockquote><p>JSONL also plays nicely with the tools you already use. Python&#8217;s standard library handles it. Pandas has read_json with lines=True. Spark reads it natively. Every major cloud platform, AWS, GCP, Azure, supports JSONL in their data ingestion services.</p></blockquote><h3>JSONL vs. the Alternatives: Picking the Right Format</h3><p>No format is universally best. Choosing between JSONL and its peers depends on what you&#8217;re optimizing for. Here&#8217;s an honest comparison.</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/nBw42/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6e662842-d386-48f0-8d1d-3cc67b70e11b_1220x880.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9a0a75fe-e700-4898-9d0b-998a2f39c5f4_1220x950.png&quot;,&quot;height&quot;:378,&quot;title&quot;:&quot;Picking the Right Format&quot;,&quot;description&quot;:&quot;Create interactive, responsive &amp; beautiful charts &#8212; no code required.&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/nBw42/1/" width="730" height="378" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><h5>JSONL vs. CSV : </h5><p>CSV works great for flat, tabular data. But the moment you need nested objects, arrays inside fields, or variable schemas across records, CSV falls apart. It also has no real standard for escaping. Quotes inside quoted strings? Good luck. JSONL handles hierarchical data naturally because each line is just JSON. If your data is strictly flat and headed for a spreadsheet, CSV is fine. For everything else, JSONL is more reliable.</p><h5>JSONL vs. Standard JSON:</h5><p> Standard JSON forces you to load the entire array to parse a single record. Appending a new record means rewriting the file. JSONL lets you append with a simple file write and parse record by record. For small config files or API responses, standard JSON is perfectly fine. For anything over a few hundred megabytes, JSONL is the practical choice.</p><h5>JSONL vs. Parquet:</h5><p> This isn&#8217;t really a competition; they solve different problems. Parquet is columnar, compressed, and optimized for analytical queries. It&#8217;s the gold standard for data lakes and warehouses. JSONL is a transit format. You use it to move data, ingest records, and feed pipelines. You can read a JSONL file with cat and a text editor. Reading Parquet requires specialized tooling. In most pipelines, data arrives as JSONL and lands as Parquet.</p><h5>JSONL vs. Avro: </h5><p>Avro enforces schemas and supports schema evolution, which matters for governance-heavy environments. It&#8217;s native to Kafka and the Hadoop ecosystem. JSONL is schema-free, which makes prototyping fast but can bite you at scale when records start drifting. If you need strict contracts between producers and consumers, Avro is the better pick.</p><h5>JSONL vs. Protocol Buffers:</h5><p>Protobuf is smaller, faster to serialize, and purpose-built for low-latency service-to-service communication. But it requires .proto definitions to decode, so it&#8217;s not self-describing. JSONL is human-readable and portable, which is why data engineers prefer it for pipelines while backend engineers reach for Protobuf in gRPC services.</p><div class="pullquote"><p>The short version: pick JSONL when you need a human-readable, streamable, schema-flexible format for data exchange and AI workflows. Pick something else when you need columnar analytics (Parquet), strict schema governance (Avro), or low-latency RPC (Protobuf).</p></div><h3>Getting Started: Working with JSONL in Python</h3><p>Reading a JSONL file takes four lines of code:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;92a64f95-6aa1-4550-a7e1-cbd639666415&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import json

with open("data.jsonl", "r") as f:
    for line in f:
        record = json.loads(line)
        print(record["name"])
</code></pre></div><p>Writing one is just as simple:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;7054ff56-dcc7-4c4d-b4e8-4982ac4d75e3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import json

data = [
    {"id": 1, "name": "Alice", "role": "Engineer"},
    {"id": 2, "name": "Bob", "role": "Data Scientist"},
    {"id": 3, "name": "Ganesh", "role": "Tech Lead"}
]

with open("output.jsonl", "w") as f:
    for item in data:
        f.write(json.dumps(item) + "\n")
</code></pre></div><p>If you&#8217;re working with pandas, it&#8217;s even shorter:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;0269a986-c437-4c72-a786-c090b1c67cb6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">import pandas as pd

# Reading
df = pd.read_json("data.jsonl", lines=True)

# Writing
df.to_json("output.jsonl", orient="records", lines=True)
</code></pre></div><p>For large files where memory matters, stick with the line-by-line approach. Pandas&#8217; read_json with lines=True still loads everything into a DataFrame. If your file is 50GB, that defeats the purpose.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VLeH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VLeH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!VLeH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!VLeH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!VLeH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VLeH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4467649,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/190697818?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VLeH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!VLeH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!VLeH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!VLeH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c8f32ac-497c-4543-9ecf-bf359095eb2a_2752x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h4>Pitfalls That Will Bite You</h4><ul><li><p>Missing Newlines: <br>Every record must end with a newline character, including the last one. Skip it, and some parsers will silently drop the final record. Others will throw a cryptic error. Always write &#8220;\n&#8221; after every json.dumps().</p></li><li><p>Inconsistent Schemas: <br>JSONL doesn&#8217;t enforce a schema. That&#8217;s a feature and a trap. If record 47,000 has a field called &#8220;user_name&#8221; while every other record uses &#8220;username&#8221;, your downstream code will break in a way that&#8217;s maddening to debug. Validate your schemas before ingestion. A ten-line Python script that checks key consistency will save you hours.</p></li><li><p>Loading the whole file into memory: <br>The entire point of JSONL is line-by-line processing. If you&#8217;re calling json.load() on the whole file or using readlines(), you&#8217;ve lost the advantage. Stream it.</p></li></ul><h3>Real-World Scenario: From CSV Chaos to JSONL Clarity</h3><p>Here&#8217;s a pattern I&#8217;ve seen play out more than once. A team has a recommendation model that needs retraining. The training data lives in CSV files exported from a legacy system. The CSVs have inconsistent quoting, mixed encodings, and nested product metadata crammed into a single column as pipe-delimited strings.</p><p>The fix: convert the CSVs to JSONL. Each product record becomes a clean JSON object with properly nested attributes. Validate the output with a schema check. Feed the JSONL file directly into the model training pipeline, which reads records one at a time.</p><p>The result? Parsing errors dropped to zero. Memory consumption during training became predictable. The pipeline ran on a smaller instance size, which cut cloud costs. And when the data team needed to add a new field, they added it to the JSON objects without breaking the existing pipeline, because JSONL doesn&#8217;t care about fixed column positions.</p><h4>My Take</h4><p>I&#8217;ve used JSONL across multiple projects, from feeding NLP models in life sciences to streaming supply chain events into analytics platforms. It&#8217;s not glamorous. It won&#8217;t show up in any &#8220;Top 10 Tools&#8221; listicle. But it works, reliably, at scale, with zero dependencies.</p><p>The one thing that surprised me: how often teams overcomplicate data exchange. They reach for Avro or Parquet before they&#8217;ve even defined their schema. For early-stage pipelines and AI experimentation, JSONL gets you moving in minutes instead of days.</p><p>My recommendation: start with JSONL for ingestion and transit. Convert to Parquet or Avro when your data lands in its final home and governance requirements tighten. </p><div class="pullquote"><p>Keep your toolchain simple until complexity earns its place.</p></div><div><hr></div>]]></content:encoded></item><item><title><![CDATA[The 4 MDM Patterns That Actually Work in Real Enterprises]]></title><description><![CDATA[Millions spent on data lakes and AI, yet you still can't answer "how many customers do we have?" The real problem is your master data.]]></description><link>https://ganeshkedari.substack.com/p/the-4-mdm-patterns-that-actually</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/the-4-mdm-patterns-that-actually</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Wed, 11 Mar 2026 10:42:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!tvQA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Introduction</h1><p>Here&#8217;s a scenario I&#8217;ve seen at least a dozen times. A large enterprise has spent millions on a shiny new data lake. They&#8217;ve built dashboards. They&#8217;ve hired data scientists. They&#8217;re ready to &#8220;do AI.&#8221;</p><p>Then someone asks: &#8220;How many customers do we have?&#8221;</p><p>Three different teams give three different numbers. Finance says 12,000. Marketing says 18,000. Sales says 15,500. Everyone&#8217;s right, technically. Everyone&#8217;s using different definitions and different source systems. Nobody trusts anyone else&#8217;s data.</p><p>This isn&#8217;t a data lake problem. This isn&#8217;t even a BI problem. This is a master data problem. And until you solve it, every analytics investment you make sits on a shaky foundation.</p><blockquote><p>Master Data Management, or MDM, has been around for decades. But most organizations still get it wrong because they think of MDM as software. It&#8217;s not. It&#8217;s an architecture decision. More specifically, it&#8217;s a choice between four practical patterns, each with distinct trade-offs.</p></blockquote><p></p><p>Let me walk you through them.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tvQA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tvQA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!tvQA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!tvQA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!tvQA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tvQA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/abb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4198497,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/190592538?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tvQA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!tvQA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!tvQA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!tvQA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb8c145-972b-4184-b5f4-01caee7ee14e_2752x1536.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Core Idea: What Is Master Data, Anyway?</h3><p>Before we talk patterns, we need to get precise about terminology.</p><p>Master data represents the core business entities that get shared across multiple systems. Customers, products, suppliers, locations, employees. These entities aren&#8217;t transactional. They don&#8217;t change moment to moment like sales orders or log files. They&#8217;re the stable reference points that transactions attach to.</p><p>The trouble starts when each system in your enterprise maintains its own version of these entities. Your CRM has a customer record. Your billing system has another. Marketing has one too. Support has their version. Now multiply that across thousands or millions of customers.</p><p>Without MDM, you end up with fragmented identities. Customer A exists as five different records across five different systems. Good luck building a customer 360 view on that foundation.</p><p>With MDM, you establish a &#8220;<a href="https://profisee.com/blog/what-is-a-golden-record/">golden record</a>&#8221;: one trusted, authoritative version of each entity. Simple in concept. Surprisingly hard in practice.</p><h4>Real-World Experience: The Architect&#8217;s Dilemma</h4><p>I&#8217;ve worked on MDM implementations across life sciences, supply chain, manufacturing, and telecom. Every time, the technical conversations are the easy part. The hard part is answering a single question: where should the master truth live?</p><p>In one manufacturing engagement, product data sat in five different systems. The PLM team owned product design. The ERP team owned product costing. E-commerce owned product descriptions. Nobody wanted to give up control. Nobody trusted anyone else&#8217;s data quality.</p><p>We spent three weeks in workshops before writing a single line of code. Not because the technology was complex. Because organizational ownership of master data is political. The architecture pattern you choose has to fit your organization&#8217;s governance maturity, not just your technical requirements.</p><p>That&#8217;s where the four MDM patterns come in.</p><div><hr></div><h1>Framework: The Four Practical MDM Patterns</h1><h3>Pattern 1: Registry MDM</h3><p>Think of this as a phone book. Each source system keeps its own customer record. The MDM system doesn&#8217;t store the data itself. It just maintains a cross-reference index linking all those disparate records to a common identifier.</p><p>CRM says Customer C123. ERP says Customer E982. Support says Customer S455. The registry says: &#8220;These are all the same person, and the MDM ID is M001.&#8221;</p><p>When to use it: You&#8217;re early in your MDM journey. Your systems are highly distributed. You need to show value quickly without disrupting operations.</p><p>Trade-off: Low effort, low governance. Data quality still depends entirely on source systems. You&#8217;ve built a map, but you haven&#8217;t cleaned the territory.</p><h3>Pattern 2: Consolidation MDM</h3><p>Here, you copy master data from source systems into a central MDM hub. The hub matches records, removes duplicates, applies survivorship rules, and produces a golden record.</p><p>But here&#8217;s the catch: that golden record only flows downstream to analytics. Operational systems keep using their original data.</p><p>When to use it: Customer 360 initiatives. Data warehouse projects. Any analytics use case that needs a unified view.</p><p>Trade-off: Great for reporting. But your operational systems remain inconsistent. Your sales rep sees one customer address. Your support rep sees another. You&#8217;ve solved the analytics problem while leaving the operational problem untouched.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XGJX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XGJX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!XGJX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!XGJX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!XGJX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XGJX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5135922,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/190592538?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XGJX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!XGJX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!XGJX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!XGJX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab0afa2-246d-4b09-b272-aa5e4c546ef6_2752x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>Pattern 3: Coexistence MDM</h3><p>This is the hybrid model. Master data can be created or modified in operational systems AND in the MDM hub. Updates flow both directions.</p><p>Think of it as a negotiated truth. The PLM system defines product specs. The MDM hub validates and standardizes them. The final master record gets distributed to ERP, supply chain, and e-commerce.</p><p>When to use it: You have moderate governance maturity. Product and supplier domains often fit this pattern well.</p><p>Trade-off: Better governance than consolidation. But integration complexity increases significantly. Synchronization conflicts become a real concern. You need robust change data capture and conflict resolution rules.</p><h3>Pattern 4: Centralized (Transactional) MDM</h3><p>The MDM platform becomes the system of record. Period. All create, update, and delete operations happen through the MDM system. Every other system consumes this data as a subscriber.</p><p>When to use it: Highly regulated industries. Compliance-sensitive domains. Organizations with mature data governance.</p><p>Trade-off: Strongest governance. True single source of truth. But it also requires the highest implementation complexity and organizational alignment. You&#8217;re asking every system owner to give up control. That&#8217;s not a technical challenge. That&#8217;s a change management challenge.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8dKv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8dKv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!8dKv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!8dKv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!8dKv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8dKv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4033007,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/190592538?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8dKv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!8dKv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!8dKv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!8dKv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F785c917e-bdd1-475a-8bfd-014452fbb1e3_2752x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>Practical Example: A Multi-Pattern Reality</h3><p>Here&#8217;s something the textbooks don&#8217;t emphasize enough: most real enterprises run multiple patterns simultaneously.</p><p>I worked with a global retailer where customer data used the consolidation pattern. They needed a unified customer view for analytics and marketing personalization. Operational systems stayed independent because rewriting POS and e-commerce platforms wasn&#8217;t practical.</p><p>Product data used the coexistence pattern. Product managers created items in the merchandising system. The MDM hub standardized hierarchies and pushed enriched data to downstream channels.</p><p>Supplier data used the centralized pattern. Regulatory compliance required a single system of record for vendor onboarding, risk assessment, and audit trails.</p><p>Three domains. Three patterns. One MDM strategy.</p><div class="pullquote"><p>The lesson? Don&#8217;t pick a pattern because it sounds &#8220;more mature.&#8221; Pick the pattern that fits each domain&#8217;s governance requirements and operational realities.</p></div><h3>What This Means for BI to AI Transformation</h3><p>If you&#8217;re on a modernization journey from BI toward AI, here&#8217;s the uncomfortable truth: your AI models are only as good as your entity resolution.</p><p>Duplicate customers corrupt your propensity models. Missing supplier relationships break your supply chain forecasts. Fragmented product hierarchies produce nonsense recommendations.</p><p>I&#8217;ve seen organizations invest heavily in ML infrastructure while ignoring master data. They end up with sophisticated models trained on garbage entities. The predictions work great in notebooks. They fail spectacularly in production because nobody mapped Customer A to Customer A across systems.</p><p>MDM isn&#8217;t glamorous. It won&#8217;t win you conference talks. But it determines whether your AI investments generate real business value or just impressive demos.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sLRQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sLRQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!sLRQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!sLRQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!sLRQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sLRQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1910709,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/190592538?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sLRQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!sLRQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!sLRQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!sLRQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eb1aade-4664-4921-8559-08ba5969a81d_2752x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>TLDR;</h2><p>AI may define the future of enterprise intelligence.</p><p>But master data determines whether that intelligence can be trusted.</p><p>If you&#8217;re building a data platform without solving the master data problem first, you&#8217;re constructing a skyscraper on sand. It might look impressive for a while. Eventually, the foundation fails.</p><p>Start with the boring work. Define your domains. Choose your patterns. Establish ownership. Then build your analytics and AI on top of that solid ground.</p><p>Your future data scientists will thank you.</p>]]></content:encoded></item><item><title><![CDATA[What Data Strategy Really Means (And Why Most Companies Get It Wrong)]]></title><description><![CDATA[Most organizations confuse data activity with data strategy. One keeps the lights on. The other builds competitive advantage. Here's how to tell the difference.]]></description><link>https://ganeshkedari.substack.com/p/what-data-strategy-really-means-and</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/what-data-strategy-really-means-and</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Mon, 02 Mar 2026 09:50:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CSns!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>The Moment I Knew Something Was Off</h3><p>A few years back, I walked into a project kickoff where the client proudly announced they had a &#8220;data strategy.&#8221; Curious, I asked what it looked like.</p><p>They pulled up a slide with three new BI tools, a Power BI rollout plan, and a request for two more data engineers.</p><p>That wasn&#8217;t a strategy. That was a shopping list.</p><p>I&#8217;ve seen this pattern more times than I can count across life sciences, supply chain, manufacturing, and telecom. Smart people, good intentions, real budgets, and yet the outcome is always the same: more dashboards, same decisions, no competitive edge.</p><p>So, let&#8217;s fix the definition.</p><div><hr></div><h3>What Data Strategy Is NOT</h3><p>Before we build the right picture, let&#8217;s clear out the clutter.</p><p>Data strategy is not tool selection. Picking Snowflake over Redshift is an architecture decision, not a strategy. Data strategy is not a dashboard redesign. If your only output is a prettier report, you&#8217;ve just redecorated, not rebuilt. It&#8217;s not a governance PDF that lives in SharePoint and gets reviewed once a year. And it&#8217;s definitely not hiring three data engineers and calling it a transformation.</p><p>These are all real things I&#8217;ve watched companies do while calling it &#8220;data strategy.&#8221; They feel productive. They rarely move the needle.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!szuB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!szuB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!szuB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!szuB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!szuB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!szuB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6707853,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/189633033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!szuB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!szuB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!szuB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!szuB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb33880cd-495d-4cb8-82c3-08c7de7c3b14_2752x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h3>What Data Strategy Actually Is</h3><p>Here&#8217;s a definition worth writing down:</p><blockquote><p><strong>Data strategy is the deliberate design of how data creates business advantage.</strong></p></blockquote><p>Not just insight. Not just visibility. Advantage, as in faster decisions, better products, lower risk, or new revenue.</p><p>To get there, strategy has to work across four pillars, not just one.</p><p><strong>Architecture</strong> is the foundation. How is data collected, stored, integrated, and accessed? Without a clean foundation, everything downstream is expensive to build and fragile to maintain. I&#8217;ve seen companies spend millions on analytics tools sitting on top of messy, undocumented data lakes. The tools aren&#8217;t the problem. The ground they&#8217;re built on is.</p><p><strong>Governance</strong> is trust. Who owns the data? What does &#8220;customer&#8221; mean in your organization? Is the definition consistent across sales, finance, and product? Governance sounds boring until a regulatory audit arrives, or until two teams present contradictory numbers to the same executive in the same meeting. Both scenarios are equally awkward.</p><p><strong>Analytics</strong> is insight, yes, but insight with purpose. Descriptive reporting tells you what happened. Diagnostic analysis tells you why. Predictive models tell you what&#8217;s likely next. Most companies live almost entirely in the first bucket and wonder why their data team doesn&#8217;t feel strategic.</p><p><strong>AI Enablement</strong> is intelligence. This is where metadata quality, data lineage, and feature engineering matter. You can&#8217;t retrofit AI onto a poorly governed data estate and expect it to work well. AI doesn&#8217;t fix messy data. It amplifies it, in the wrong direction.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CSns!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CSns!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!CSns!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!CSns!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!CSns!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CSns!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6783971,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/189633033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CSns!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!CSns!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!CSns!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!CSns!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87bf81bc-e201-4ffc-b351-a1f878328301_2752x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h3>Why Most Companies Get It Wrong</h3><p>They start with visualization instead of architecture. It&#8217;s an understandable mistake. Dashboards are visible. You can demo them to leadership. Architecture work is invisible until something breaks.</p><p>They ignore ownership. Data without an owner is data nobody trusts. And data nobody trusts gets ignored, which defeats the entire purpose.</p><p>They underestimate compliance. GDPR, HIPAA, FDA 21 CFR Part 11, depending on your industry, compliance isn&#8217;t a constraint you add later. It shapes the architecture from the start. I&#8217;ve seen projects get rebuilt from scratch because governance was treated as an afterthought.</p><p>And most critically, they plan to &#8220;add AI later.&#8221; This is the fastest way to ensure AI never works well in your organization. AI readiness isn&#8217;t a phase you reach. It&#8217;s a property you design in from the beginning.</p><div><hr></div><h3>A Real Pattern I&#8217;ve Seen&#8230;</h3><p>Imagine a mid-size manufacturing company. They have reports. Lots of them. Production reports, quality reports, supply chain reports. Analysts spend forty percent of their time pulling data and building slides. Leadership waits two weeks for answers to questions that should take two minutes.</p><p>That&#8217;s a reactive reporting organization. Data serves the past.</p><p>Now imagine the same company three years into a proper data strategy. Sensor data from the factory floor feeds a real-time quality model. Supply chain disruptions trigger automated alerts before shipments are missed. The ERP, the MES, and the CRM finally share a common customer and product definition. Analysts are now working on forecasting models, not pivot tables.<br><br>That&#8217;s a predictive system. Data serves the future.</p><p>The difference isn&#8217;t the tools. It&#8217;s the strategy behind the tools.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MzaV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MzaV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!MzaV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!MzaV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!MzaV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MzaV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6285517,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/189633033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MzaV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!MzaV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!MzaV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!MzaV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F597f30ea-a750-4509-aef2-78470726f4c0_2752x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h3>The BI to AI Bridge</h3><p>Here&#8217;s something worth sitting with:  every company I&#8217;ve worked with that successfully made the leap from BI to AI had one thing in common. They had designed their data estate with AI in mind before they ever deployed a model.</p><p>That means metadata was captured, not just tolerated. Data lineage was tracked so you could explain where a number came from. Business definitions were standardized across domains. Ownership was clear. Access controls were in place.</p><p>None of that sounds like AI. All of it is required for AI to work.</p><p>The organizations that skip this step and jump straight to &#8220;let&#8217;s build a model&#8221; end up with models that can&#8217;t be explained, can&#8217;t be trusted, and can&#8217;t be deployed into production. The data scientists are frustrated. The business is skeptical. The whole initiative stalls.</p><p>Strategy that anticipates AI from day one builds the plumbing that AI needs to run. That&#8217;s the bridge from BI to AI, and it has to be designed, not discovered.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!S5Dj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!S5Dj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!S5Dj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!S5Dj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!S5Dj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!S5Dj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6938986,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/189633033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!S5Dj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!S5Dj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!S5Dj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!S5Dj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b9b3530-890e-410a-a75f-62ad7e52d022_2752x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h3>Closing Thought</h3><p>Data strategy is not about dashboards. It&#8217;s about decision velocity.</p><p>The companies that get this right don&#8217;t just see their data better. They move faster, adapt quicker, and build things their competitors can&#8217;t easily copy. That&#8217;s the actual prize.</p><p>Everything else is just a report.</p>]]></content:encoded></item><item><title><![CDATA[Why Your SQL Is Slow, Hint: It’s Not Missing an Index]]></title><description><![CDATA[Don&#8217;t optimize SQL early. First fix joins, prune columns, push filters. THEN add clustering or partitioning. Big wins come from logic, not magic.]]></description><link>https://ganeshkedari.substack.com/p/why-your-sql-is-slow-hint-its-not</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/why-your-sql-is-slow-hint-its-not</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Fri, 05 Dec 2025 03:45:30 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ac54a327-f420-4625-845f-ca3ebbe2ca94_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>SQL tuning has become a sport. People trade theories about partition keys, materialized views, and clustering as if they&#8217;re rare collectibles. In practice, most queries slow down for boring reasons. They join too much, pull too much, or filter too late. The engine isn&#8217;t confused, it&#8217;s just doing exactly what you asked.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VDYq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VDYq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png 424w, https://substackcdn.com/image/fetch/$s_!VDYq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png 848w, https://substackcdn.com/image/fetch/$s_!VDYq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png 1272w, https://substackcdn.com/image/fetch/$s_!VDYq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VDYq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png" width="2367" height="1153" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1153,&quot;width&quot;:2367,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4157811,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/180688528?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a47d43e-0f63-419c-9edd-61f83b4f8f29_2816x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VDYq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png 424w, https://substackcdn.com/image/fetch/$s_!VDYq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png 848w, https://substackcdn.com/image/fetch/$s_!VDYq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png 1272w, https://substackcdn.com/image/fetch/$s_!VDYq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F213b021d-6914-4f97-ac6e-ef4f92e25f69_2367x1153.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>This matters now because cloud warehouses charge with a stopwatch. A badly written query doesn&#8217;t just waste time, it burns compute credits. When teams move fast, SQL mistakes slip in quietly, and they grow legs inside production pipelines.</p><p>Most SQL performance issues have the same root causes. If you fix the joins, prune the columns, and push the filters early, you solve almost everything. These three habits deliver bigger gains than any exotic index or clustering pattern. You don&#8217;t need wizardry. You need cleaner thinking.</p><div><hr></div><h2><strong>Real-World Experience</strong></h2><p>Across life sciences, supply chain, manufacturing, and telecom, I&#8217;ve seen clever engineers try elaborate tweaks while a single cross join ruins their dashboards. I&#8217;ve seen wide tables dragged across the warehouse because someone typed <code>SELECT *</code> during a late-night experiment. I&#8217;ve seen filters applied last, long after the data has ballooned to absurd size.</p><p>One telecom analytics team once spent a week debating the ideal clustering key. Their actual issue was an accidental many-to-many join. Removing that single join condition dropped their runtime from thirty minutes to under fifteen seconds. No tuning trick could compete with plain correctness.</p><div><hr></div><h2><strong>Framework: The Three-Step Reality Check</strong></h2><p>The easiest way to write fast SQL is to follow a small ritual. I call it the Reality Check. It grounds your query in three simple questions.</p><h3><strong>1. Fix the joins</strong></h3><p>Most slow queries begin with joins that multiply rows or pull unnecessary data. Engines are good at many things, but they can&#8217;t save a flawed join.</p><p><strong>Example:</strong><br>A supply-chain report joins <code>inventory</code> with <code>products</code> only by <code>product_id</code>, ignoring the fact that the inventory table tracks data per day.</p><pre><code><code>SELECT *
FROM inventory i
JOIN products p
  ON i.product_id = p.product_id;
</code></code></pre><p>This blows up into a many-to-many mess. Add the missing date condition, and the difference is night and day.</p><pre><code><code>SELECT *
FROM inventory i
JOIN products p
  ON i.product_id = p.product_id
 AND i.valid_date = p.active_date;
</code></code></pre><p>The first query produced forty-two million rows. The corrected join produced three hundred thousand. Nothing magical, just accurate logic.</p><div><hr></div><h3><strong>2. Prune the columns</strong></h3><p>Most warehouses don&#8217;t struggle with wide tables. They struggle with queries that read every column out of them. Pulling unnecessary data is the fastest way to burn compute.</p><p><strong>Example:</strong><br>A BI developer builds a sales report with a materialized view containing 280 columns. The dashboard uses four of them, yet the query fetches everything.</p><pre><code><code>SELECT *
FROM mv_sales_enriched
WHERE sales_date BETWEEN &#8216;2024-01-01&#8217; AND &#8216;2024-01-31&#8217;;
</code></code></pre><p>A trimmed version is far kinder to the warehouse and your wallet.</p><pre><code><code>SELECT sales_id, region, product_id, sales_amount
FROM mv_sales_enriched
WHERE sales_date BETWEEN &#8216;2024-01-01&#8217; AND &#8216;2024-01-31&#8217;;
</code></code></pre><p>This cut compute usage by more than seventy percent in one real engagement. It wasn&#8217;t optimization, it was common sense.</p><div><hr></div><h3><strong>3. Push your filters up</strong></h3><p>Many queries try to do all their joins first, then filter the results at the end. That&#8217;s the SQL equivalent of cleaning your sink after you fill it with dishes.</p><p>Filtering early keeps the data small and the joins light.</p><p><strong>Example:</strong><br>A manufacturing analytics job joins three dimension tables with a fact table, then applies the date filter last.</p><pre><code><code>SELECT d1.attr, d2.attr, f.*
FROM fact_production f
JOIN dim_machine d1 ON f.machine_id = d1.id
JOIN dim_shift d2 ON f.shift_id = d2.id
WHERE f.production_date &gt;= &#8216;2024-01-01&#8217;;
</code></code></pre><p>Flipping the order makes the engine&#8217;s life easier.</p><pre><code><code>WITH base AS (
  SELECT *
  FROM fact_production
  WHERE production_date &gt;= &#8216;2024-01-01&#8217;
)
SELECT d1.attr, d2.attr, b.*
FROM base b
JOIN dim_machine d1 ON b.machine_id = d1.id
JOIN dim_shift d2 ON b.shift_id = d2.id;
</code></code></pre><p>A global manufacturing team saw runtime drop from eleven minutes to forty seconds using this simple rearrangement.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4hwq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4hwq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png 424w, https://substackcdn.com/image/fetch/$s_!4hwq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png 848w, https://substackcdn.com/image/fetch/$s_!4hwq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png 1272w, https://substackcdn.com/image/fetch/$s_!4hwq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4hwq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png" width="686" height="421" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:421,&quot;width&quot;:686,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:35609,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4hwq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png 424w, https://substackcdn.com/image/fetch/$s_!4hwq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png 848w, https://substackcdn.com/image/fetch/$s_!4hwq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png 1272w, https://substackcdn.com/image/fetch/$s_!4hwq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F675ca6ba-bc76-42fb-b204-53b7bd757676_686x421.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2><strong>Practical Example</strong></h2><p>Let&#8217;s say a sales pipeline pulls data from a one-billion-row fact table and several dimensions. The original version joins all the dimensions first, selects every column, and applies the date filter at the very end. It runs in twelve minutes and occasionally times out.</p><p>Rewrite it using the Reality Check.</p><blockquote><p><em>Filter the fact table first.</em><br><em>Select only the columns the pipeline actually needs.</em><br><em>Join the dimensions after the data is already small.</em></p></blockquote><p>The updated query runs in under a minute. No partitions, no indexes, no warehouse upgrades. Just logic restored to sanity.</p><div><hr></div><h2><strong>What This Means for BI &#8594; AI Transformation</strong></h2><p>Every AI system depends on fresh, predictable data. When SQL slows down, everything from dashboards to features to model retraining slows with it. Clean SQL makes pipelines cheaper, simpler, and more reliable. That stability is the hidden foundation for any BI to AI transformation.</p><p>You can&#8217;t build futuristic analytics on top of chaotic joins and wide selects. AI loves clean data, but it loves predictable pipelines even more.</p><div class="pullquote"><p>Fast SQL doesn&#8217;t require wizardry. It just needs clarity, restraint, and a refusal to drag the entire warehouse into every query.</p></div>]]></content:encoded></item><item><title><![CDATA[What You Need to Know About ijson: The Smart Way to Handle Large JSON]]></title><description><![CDATA[Learn how to process massive JSON files without loading everything into memory - perfect for data engineers dealing with APIs, logs, and event streams that don't fit in RAM.]]></description><link>https://ganeshkedari.substack.com/p/what-you-need-to-know-about-ijson</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/what-you-need-to-know-about-ijson</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Thu, 04 Dec 2025 09:39:13 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6c872d9e-7eda-45e3-87c2-9a2da89119c3_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Picture this: You&#8217;re excited about a new data source - maybe it&#8217;s event logs from your application, a massive API response, or a JSON export from your database. You write a quick Python script with <code>json.load()</code>, hit run, and... your script crashes. Out of memory. The JSON file is 2GB, and Python just tried to load all of it into RAM at once.</p><p>Sound familiar?</p><p>This is the pain point that <strong>ijson</strong> solves elegantly. In a world where APIs return JSON by default and microservices communicate in JSON, we often forget that JSON wasn&#8217;t designed for gigabyte-scale files. But our data doesn&#8217;t care about design intentions - it just keeps growing.</p><p>ijson is a Python library that lets you <strong>stream</strong> JSON data incrementally, processing it piece by piece without ever loading the entire structure into memory. Think of it as the difference between trying to drink from a fire hose versus sipping through a straw.</p><div><hr></div><h2><strong>What is ijson ? </strong></h2><p><a href="https://pypi.org/project/ijson/">ijson </a>is a <strong>streaming JSON parser</strong> for Python. Instead of reading an entire JSON file into memory like the standard <code>json</code> library does, ijson reads the file <strong>incrementally</strong> pulling out just the pieces you need, when you need them.</p><p>Here&#8217;s a friendly analogy: Imagine you&#8217;re reading a massive encyclopedia. The standard <code>json.load()</code> approach is like photocopying the entire encyclopedia before you start reading. ijson is like opening the book and reading one page at a time, taking notes on just the information you need.</p><p>Under the hood, ijson uses an <strong>event-driven parsing model</strong> (similar to SAX parsers for XML, if you remember those days). As it reads through the JSON, it fires events like &#8220;I found a key,&#8221; &#8220;I found a value,&#8221; &#8220;I&#8217;m entering an array.&#8221; You listen for the events you care about and ignore the rest.</p><p><strong>The key insight:</strong> You don&#8217;t need the entire data structure in memory to extract what you need.</p><div><hr></div><h2><strong>Importance of ijson</strong></h2><p>As a Solution Architect who&#8217;s modernized countless data pipelines, I see ijson filling a critical gap in the data engineering toolkit.</p><p><strong>Where it shines:</strong></p><ul><li><p><strong>API integrations</strong> that return paginated results in multi-megabyte JSON responses</p></li><li><p><strong>Log processing</strong> where JSON-formatted logs can grow to gigabytes per day</p></li><li><p><strong>Data lake ingestion</strong> where raw JSON files from S3 need parsing before transformation</p></li><li><p><strong>Real-time event streams</strong> saved as JSON need selective extraction without full deserialization</p></li></ul><p><strong>Business impact:</strong></p><ul><li><p><strong>Cost savings:</strong> Process data on smaller machines (less RAM = lower cloud costs)</p></li><li><p><strong>Reliability:</strong> No more out-of-memory crashes during production runs</p></li><li><p><strong>Speed:</strong> For selective queries, streaming is often faster than loading everything</p></li><li><p><strong>Scalability:</strong> Handle files that are literally too large to fit in memory</p></li></ul><p>ijson represents a mature understanding that <strong>not all data needs to be fully loaded to be useful</strong>. It&#8217;s the difference between naive data handling and production-grade engineering.</p><div><hr></div><h2><strong>Core Components</strong></h2><p>ijson has a beautifully simple API built around a few core concepts:</p><p><strong>1. Items Generator</strong> The workhorse of ijson. You provide a <strong>path</strong> (like a query) to specify what you want, and ijson yields matching items one at a time.</p><p><strong>2. Parse Events</strong> Lower-level interface that fires events for every JSON element encountered: <code>start_map</code>, <code>map_key</code>, <code>string</code>, <code>number</code>, <code>end_array</code>, etc.</p><p><strong>3. Backend Engines</strong> ijson supports multiple parsing backends (pure Python, yajl2, yajl2-cffi). The backend determines speed vs. compatibility.</p><p><strong>4. Path Specification</strong> A simple dot-notation syntax to navigate JSON structure:</p><ul><li><p><code>item</code> = top-level items</p></li><li><p><code>users.item</code> = each item in a &#8220;users&#8221; array</p></li><li><p><code>data.records.item.name</code> = the &#8220;name&#8221; field from each record</p></li></ul><p><strong>How it works under the hood:</strong> ijson uses a <strong>push parser</strong> that reads the JSON file in chunks (like reading a book one paragraph at a time). It maintains minimal state about where it is in the JSON structure (current nesting level, current path) and matches that against your query path. When there&#8217;s a match, it yields the data. Everything else is discarded immediately.</p><div><hr></div><h2><strong>How to Get Started</strong></h2><p><strong>Prerequisites:</strong></p><ul><li><p>Python 3.6+ (though 3.8+ recommended)</p></li><li><p>A JSON file larger than you&#8217;d want to load into memory (or just curiosity)</p></li><li><p>Basic understanding of Python generators</p></li></ul><p><strong>Installation:</strong></p><pre><code><code>pip install ijson</code></code></pre><p><strong>Your First Command:</strong> Let&#8217;s say you have a file <code>data.json</code> with this structure:</p><pre><code><code>{
  &#8220;users&#8221;: [
    {&#8221;id&#8221;: 1, &#8220;name&#8221;: &#8220;Alice&#8221;, &#8220;email&#8221;: &#8220;alice@example.com&#8221;},
    {&#8221;id&#8221;: 2, &#8220;name&#8221;: &#8220;Bob&#8221;, &#8220;email&#8221;: &#8220;bob@example.com&#8221;}
  ]
}</code></code></pre><p>Here&#8217;s your first ijson script:</p><pre><code><code>import ijson

# Open file and create a streaming parser
with open(&#8217;data.json&#8217;, &#8216;rb&#8217;) as file:
    # Extract each user object from the users array
    users = ijson.items(file, &#8216;users.item&#8217;)
    
    # Process one user at a time
    for user in users:
        print(f&#8221;User: {user[&#8217;name&#8217;]} - {user[&#8217;email&#8217;]}&#8221;)</code></code></pre><p>That&#8217;s it! Notice:</p><ul><li><p>File opened in <strong>binary mode</strong> (<code>&#8216;rb&#8217;</code>) - ijson needs bytes</p></li><li><p>Path <code>&#8216;users.item&#8217;</code> means &#8220;each item in the users array&#8221;</p></li><li><p>Memory usage stays constant regardless of file size</p></li></ul><div><hr></div><h2><strong>A Small Hands-On Example</strong></h2><p><strong>Real-World Scenario:</strong> You&#8217;re processing GitHub API event logs. Each file is 500MB of JSON containing thousands of events. You only care about &#8220;push&#8221; events from a specific repository.</p><p><strong>Sample structure:</strong></p><pre><code><code>{
  &#8220;events&#8221;: [
    {&#8221;type&#8221;: &#8220;PushEvent&#8221;, &#8220;repo&#8221;: &#8220;myorg/myrepo&#8221;, &#8220;commits&#8221;: 3},
    {&#8221;type&#8221;: &#8220;IssueEvent&#8221;, &#8220;repo&#8221;: &#8220;other/repo&#8221;, &#8220;commits&#8221;: 0},
    {&#8221;type&#8221;: &#8220;PushEvent&#8221;, &#8220;repo&#8221;: &#8220;myorg/myrepo&#8221;, &#8220;commits&#8221;: 5}
  ]
}</code></code></pre><p><strong>Solution with ijson:</strong></p><pre><code><code>import ijson

def process_push_events(filename, target_repo):
    push_count = 0
    total_commits = 0
    
    with open(filename, &#8216;rb&#8217;) as file:
        # Stream through events one at a time
        events = ijson.items(file, &#8216;events.item&#8217;)
        
        for event in events:
            if event.get(&#8217;type&#8217;) == &#8216;PushEvent&#8217; and \
               event.get(&#8217;repo&#8217;) == target_repo:
                push_count += 1
                total_commits += event.get(&#8217;commits&#8217;, 0)
    
    return push_count, total_commits

# Process a 500MB file using ~10MB of RAM
count, commits = process_push_events(&#8217;github_events.json&#8217;, &#8216;myorg/myrepo&#8217;)
print(f&#8221;Found {count} push events with {commits} total commits&#8221;)</code></code></pre><p><strong>Why this matters:</strong> Without ijson, you&#8217;d need 500MB+ of RAM to load the file. With ijson, you use ~10MB just enough to hold one event at a time.</p><div><hr></div><h2><strong>Common Pitfalls (And How to Avoid Them)</strong></h2><p><strong>1. Forgetting Binary Mode</strong></p><p>python</p><pre><code><code># &#10060; Won&#8217;t work
with open(&#8217;data.json&#8217;, &#8216;r&#8217;) as file:

# &#9989; Always use binary mode
with open(&#8217;data.json&#8217;, &#8216;rb&#8217;) as file:</code></code></pre><p><strong>2. Incorrect Path Specification</strong> The path <code>users</code> returns the entire array. The path <code>users.item</code> returns each user individually. Test your paths on small files first.</p><p><strong>3. Trying to Seek or Restart</strong> ijson streams are <strong>forward-only</strong>. If you need to make multiple passes, either:</p><ul><li><p>Read the file multiple times (if disk I/O is cheap)</p></li><li><p>Collect data in the first pass and process from memory</p></li></ul><p><strong>4. Performance Expectations</strong> ijson is optimized for <strong>memory efficiency</strong>, not raw speed. For small files (&lt;100MB), standard <code>json.load()</code> is faster. Use ijson when memory is the bottleneck.</p><p><strong>5. Backend Selection</strong> The pure Python backend is slower but has no dependencies. For production:</p><pre><code><code>pip install ijson[yajl2]
import ijson.backends.yajl2 as ijson  # Much faster</code></code></pre><div><hr></div><h2><strong>What&#8217;s Next? </strong></h2><p><strong>After mastering the basics:</strong></p><ol><li><p><strong>Explore parse events</strong> for custom processing logic</p></li><li><p><strong>Combine with multiprocessing</strong> to parse multiple files in parallel</p></li><li><p><strong>Integrate with data quality checks</strong> during streaming</p></li><li><p><strong>Build JSON-to-Parquet converters</strong> that stream end-to-end</p></li><li><p><strong>Study similar tools:</strong> <code>jq</code> for command-line, <code>simdjson</code> for speed, <code>jsonlines</code> for JSONL format</p></li></ol><p><strong>Resources:</strong></p><ul><li><p>ijson documentation: <a href="https://github.com/ICRAR/ijson">https://github.com/ICRAR/ijson</a></p></li><li><p>Practice with public datasets: GitHub Archive, Common Crawl samples</p></li><li><p>Experiment with different backends to understand performance trade-offs</p></li></ul><p><strong>Pro tip:</strong> Once you understand streaming JSON, the same concepts apply to XML (lxml), CSV (chunked pandas), and even binary formats (protobuf streaming).</p><div><hr></div><h2><strong>Closing Thought</strong></h2><p>Every elegant solution starts with recognizing a constraint. ijson doesn&#8217;t fight the fact that large JSON files exist , it works <em>with</em> that reality, turning a potential blocker into a manageable challenge.</p><p>As you build systems that bridge BI and AI, remember: sometimes the most powerful optimization isn&#8217;t making things faster , it&#8217;s making them <em>possible</em>. That&#8217;s what ijson does.</p><p>Happy streaming! </p>]]></content:encoded></item><item><title><![CDATA[Five Architecture Mistakes That Turn AI Promises Into Technical Debt]]></title><description><![CDATA[Most AI adoption failures aren't about algorithms or data scientists , they're about the invisible architecture decisions made months before the first model runs.]]></description><link>https://ganeshkedari.substack.com/p/five-architecture-mistakes-that-turn</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/five-architecture-mistakes-that-turn</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Wed, 03 Dec 2025 19:52:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!jHsq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most AI failures have nothing to do with algorithms or the people training them. They come from quiet architecture choices made months before the first model sees daylight.</p><p>I&#8217;ve reviewed enough AI plans to see the same move every time. Companies hire data scientists, spin up GPUs, chase the newest LLM tricks, and assume the heavy lifting is done. Meanwhile, their architecture calmly loads the entire project with friction until it buckles.</p><p>These aren&#8217;t exotic problems. They&#8217;re the same traps that slowed BI programs years ago, just magnified by AI&#8217;s hunger for compute, data, and fast iteration. After sixteen years building systems across life sciences, manufacturing, and telecom, I can tell you exactly where things go wrong and how to avoid the usual facepalm moments.</p><p>Let&#8217;s look at the five mistakes that sink AI work, along with the fixes that keep your stack from turning into an expensive science fair.</p><p><strong>Architecture as the Invisible Handbrake</strong></p><div class="pullquote"><p>AI efforts rarely collapse because the models misbehave. They collapse because the architecture can&#8217;t keep up with the constant training, experimentation, and deployment that modern AI depends on.</p></div><p>BI stacks were built for predictable workloads, rigid schemas, and monthly change cycles. AI needs quick changes, real-time inference, continuous retraining, and versioning that doesn&#8217;t require a prayer. Running AI on BI infrastructure is like asking a cargo ship to corner like a motorcycle. It won&#8217;t.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jHsq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jHsq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png 424w, https://substackcdn.com/image/fetch/$s_!jHsq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png 848w, https://substackcdn.com/image/fetch/$s_!jHsq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png 1272w, https://substackcdn.com/image/fetch/$s_!jHsq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jHsq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png" width="1906" height="1947" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1947,&quot;width&quot;:1906,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:307192,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jHsq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png 424w, https://substackcdn.com/image/fetch/$s_!jHsq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png 848w, https://substackcdn.com/image/fetch/$s_!jHsq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png 1272w, https://substackcdn.com/image/fetch/$s_!jHsq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9c43ee-1053-4672-ac18-8878de55451e_1906x1947.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Mistake 1: Treating ML Models Like Static Reports</strong></p><p>You deploy a churn model, everyone cheers, and then it gathers dust. Months pass, accuracy drifts, and nobody knows how to retrain or redeploy it.</p><p>BI culture sees outputs as artifacts, not living systems. Reports age gracefully. Models don&#8217;t. Without CI/CD for models, monitoring, automated retraining, or versioning, your shiny model becomes stale code sitting in the corner.</p><p>Treat models like microservices. Version them. Monitor them. Automate everything from training to rollback. Even a basic pipeline for validation, training, A/B testing, and recovery puts you ahead of most teams.</p><p><strong>Mistake 2: Building Data Lakes Without Thinking About Access</strong></p><p>You dump terabytes into S3 or ADLS and admire the view. Meanwhile, data scientists lose days stitching files together, guessing schemas, and writing one-off transformations.</p><p>Data lakes look flexible on paper, but without a plan for how data will be consumed, they turn into swamps. AI workloads need feature stores, not treasure hunts.</p><p>Design around consumption. Build lightweight feature stores that version features, store transformations, and support batch and real-time access. Use analytic-friendly formats like Parquet or Delta and define a clean path from raw data to curated, feature-ready datasets.</p><p>Think of the lake as your pantry and the feature store as the prep table. Nobody wants to cook straight out of the warehouse.</p><p><strong>Mistake 3: Ignoring Inference Latency Until It Bites You</strong></p><p>Your model looks elegant in a notebook. Then you push it to production and watch latency crawl to three seconds, batch inference choke during peak loads, or APIs throttle your traffic.</p><p>Training and inference aren&#8217;t siblings. They&#8217;re barely cousins. Training tolerates heavy pipelines and long cycles. Inference needs speed, uptime, and predictable behavior under load. Many teams design for training and treat inference like an afterthought.</p><p>Plan inference while you plan training. Ask early whether you need sub-100ms responses, whether you can cache predictions, and whether lightweight edge models beat a central heavyweight model. If customers see the output, aim for 99.9% uptime and tight latency budgets.</p><p><strong>Mistake 4: Letting Monolithic Data Pipelines Block Experimentation</strong></p><p>Your nightly ETL job becomes the sacred cow. Every new feature requires touching it. Nobody wants to touch it. Data scientists wait days for changes, and experimentation dies a slow, predictable death.</p><p>Legacy BI pipelines were designed for stability, not creativity. AI teams, on the other hand, need frictionless iteration.</p><p>Split pipelines into modular tasks. Use orchestration tools that allow versioning, branching, and quick rollbacks. Keep production-critical pipelines separate from experimental ones. Let data scientists own feature logic without pleading for changes to the core ETL.</p><p>Think microservices, but for data.</p><p><strong>Mistake 5: Forgetting That Compute Costs Grow Faster Than Predictions</strong></p><p>Your first model runs fine. Then you introduce five more, add regular retraining, and try hyperparameter tuning. Congratulations, your cloud bill now looks like a ransom note. The GPU cluster sits idle most of the week, but you&#8217;re afraid to shut it down.</p><p>AI compute is spiky and expensive. BI workloads are steady. Without guardrails, you overpay for idle resources and burn money during peak cycles.</p><p>Use spot instances for training. Separate training and inference infra. Compress models to cut inference costs. Monitor cost per model, not the total bill. And when managed services save more than they cost, use them.</p><p>Architect like someone who signs the invoices.</p><p></p><div><hr></div><p><strong>A Life Sciences Lesson That Still Stings</strong></p><p>One of my early clients wanted to use AI to improve clinical trial timelines. They had brilliant scientists and strong leadership support. What they didn&#8217;t have was a modern data architecture. Their EDC system pushed data into rigid marts meant for regulatory reporting, not machine learning.</p><p>Each model iteration required manual extracts, transformations, and approvals that took weeks. By the time the data arrived, the trial parameters had already changed. Nothing was wrong with the models. Everything was wrong with the plumbing.</p><p>We fixed it by adding a feature engineering layer between the EDC and the ML environment. It was governed, versioned, and flexible. Suddenly, updates took days instead of weeks. AI went from theoretical to useful.</p><p>The reality is simple. AI rarely fails because of math. It fails because of pipes.</p><p><strong>The AI-Ready Architecture Checklist</strong></p><p>Before you start another AI project, check your backbone.</p><ul><li><p>Can you version, deploy, monitor, and roll back models without heroics?</p></li><li><p>Do data scientists spend more time modeling than wrangling?</p></li><li><p>Do you know your latency requirements and have an inference setup that meets them?</p></li><li><p>Can you add experimental features without touching production ETL?</p></li><li><p>Can you track compute costs per model and prevent runaway spend?</p></li></ul><p>If you&#8217;re saying no more than yes, pause and fix the architecture. It&#8217;ll save you time, money, and conversations you&#8217;d rather not have with leadership.</p><p></p><div><hr></div><p><strong>What BI to AI Transformation Really Requires</strong></p><p>Moving from BI to AI isn&#8217;t about adding models to your stack. It&#8217;s about rethinking the whole architecture for a workload that behaves nothing like BI.</p><p>BI favored stability. AI needs iteration.<br>BI produced static artifacts. AI produces living systems.<br>BI centralized control. AI thrives with controlled experimentation.<br>BI served reports. AI serves behavior-changing predictions.<br><br></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7TF-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7TF-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png 424w, https://substackcdn.com/image/fetch/$s_!7TF-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png 848w, https://substackcdn.com/image/fetch/$s_!7TF-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png 1272w, https://substackcdn.com/image/fetch/$s_!7TF-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7TF-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png" width="1620" height="1405" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1405,&quot;width&quot;:1620,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:211173,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7TF-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png 424w, https://substackcdn.com/image/fetch/$s_!7TF-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png 848w, https://substackcdn.com/image/fetch/$s_!7TF-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png 1272w, https://substackcdn.com/image/fetch/$s_!7TF-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8df7af3-306f-4a4b-b817-68c4b2a19053_1620x1405.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you treat AI as &#8220;BI with bonus math,&#8221; you&#8217;ll struggle. If you treat it as a fundamentally different discipline, you&#8217;ll get the compounding returns everyone else keeps promising.</p><p>Your BI stack taught the business to trust data. Your AI architecture will teach it to act on intelligence. Just don&#8217;t let invisible architecture choices block the progress.</p><p><strong>Closing Line</strong></p><p>Get the architecture right, and your models will behave. Ignore it, and even the best model will act like it&#8217;s on strike.</p>]]></content:encoded></item><item><title><![CDATA[Skills for Analytics Professional in 2026: Beyond SQL and Python]]></title><description><![CDATA[The analytics profession is evolving faster than ever. Here's what you need to master now to stay relevant, valuable, and ahead of the curve in the AI-first era.]]></description><link>https://ganeshkedari.substack.com/p/skills-for-analytics-professional</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/skills-for-analytics-professional</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Fri, 28 Nov 2025 12:10:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5ae067fa-ea7e-408f-813a-4ddcf99cb45a_1587x2245.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I remember my first analytics role in 2008. The job description was straightforward: &#8220;Must know SQL, Excel, and basic statistics.&#8221; If you could write a decent JOIN and make a pivot table, you were golden.</p><p>Fast forward to 2025, and I&#8217;m helping enterprises architect AI-powered decision systems. The analytics professionals who thrive today look nothing like those from a decade ago. They&#8217;re not just data wranglers&#8212;they&#8217;re <strong>translators, orchestrators, and decision architects</strong>.</p><p>And here&#8217;s the uncomfortable truth: <strong>the skills that made you successful three years ago might not be enough in 2026.</strong></p><p>This isn&#8217;t about doom and gloom. It&#8217;s about evolution. The analytics profession is maturing, and so must we. Whether you&#8217;re a BI analyst, data engineer, or ML practitioner, the landscape ahead demands a broader, deeper skill set. Let me walk you through what matters now&#8212;not based on LinkedIn influencer hype, but from 16+ years in the trenches building real systems for real enterprises.</p><div><hr></div><h3><strong>The Three-Layer Skill Model</strong></h3><p>Here&#8217;s how I think about the modern analytics skill stack. It&#8217;s not a linear list&#8212;it&#8217;s <strong>three interconnected layers</strong>:</p><p><strong>Layer 1: Technical Foundation</strong> (Your craft)<br>The core skills that let you do the work. This is your SQL, Python, data modeling, ML fundamentals.</p><p><strong>Layer 2: Contextual Intelligence</strong> (Your judgment)<br>The ability to understand <em>why</em> the work matters, <em>how</em> it fits into business strategy, and <em>what</em> good looks like beyond accuracy metrics.</p><p><strong>Layer 3: Influence Architecture</strong> (Your impact)<br>The skills that help you get your work adopted, trusted, and scaled across the organization.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8ger!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8ger!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png 424w, https://substackcdn.com/image/fetch/$s_!8ger!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png 848w, https://substackcdn.com/image/fetch/$s_!8ger!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png 1272w, https://substackcdn.com/image/fetch/$s_!8ger!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8ger!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png" width="797" height="403" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:403,&quot;width&quot;:797,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39508,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8ger!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png 424w, https://substackcdn.com/image/fetch/$s_!8ger!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png 848w, https://substackcdn.com/image/fetch/$s_!8ger!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png 1272w, https://substackcdn.com/image/fetch/$s_!8ger!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d75350b-0a10-4eac-aeac-1f3ecaf69547_797x403.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Most analytics professionals plateau because they <strong>over-invest in Layer 1 and under-invest in Layers 2 and 3</strong>. In 2026, the gap between &#8220;good&#8221; and &#8220;great&#8221; will live in those upper layers.</p><p>Let me break down what&#8217;s essential in each.</p><div><hr></div><h3><strong>Layer 1: Technical Foundation (Still Matters, But Differently)</strong></h3><p>Yes, you still need technical chops. But the <strong>nature of technical work is changing</strong>.</p><h4><strong>1. Prompt Engineering &amp; LLM Fluency</strong></h4><p>You don&#8217;t need to build foundation models. But you absolutely need to know how to <strong>work with them productively</strong>. This means:</p><ul><li><p>Writing effective prompts for code generation, data exploration, documentation</p></li><li><p>Understanding when to use RAG (Retrieval-Augmented Generation) vs fine-tuning</p></li><li><p>Debugging AI-generated code and knowing its blind spots</p></li></ul><p>If you&#8217;re still writing boilerplate SQL or Python from scratch in 2026, you&#8217;re working too hard. The best analysts I know use AI copilots to <strong>accelerate grunt work</strong> and free up cognitive space for the hard problems.</p><h4><strong>2. Data Engineering Fundamentals (Not Optional Anymore)</strong></h4><p>The line between &#8220;analytics&#8221; and &#8220;data engineering&#8221; is blurring. You don&#8217;t need to be a Spark expert, but you should understand:</p><ul><li><p>How data pipelines work (orchestration, dependencies, failure handling)</p></li><li><p>Modern data stack concepts (dbt, ELT vs ETL, data mesh)</p></li><li><p>Data quality and observability (what breaks, how to detect it, how to fix it)</p></li></ul><p>Why? Because <strong>your insights are only as good as the data behind them</strong>. If you can&#8217;t trace lineage or debug a broken pipeline, you&#8217;re flying blind.</p><h4><strong>3. Cloud-Native Thinking</strong></h4><p>On-prem is legacy. If you&#8217;re not comfortable spinning up cloud resources, working with APIs, and understanding serverless architectures, you&#8217;re limiting your ceiling. You don&#8217;t need to be a DevOps engineer, but you should be <strong>conversationally fluent</strong> in cloud patterns.</p><div><hr></div><h3><strong>Layer 2: Contextual Intelligence (The New Differentiator)</strong></h3><p>This is where most people stumble. You can be a Python wizard, but if you can&#8217;t connect your work to business outcomes, you&#8217;re just an expensive calculator.</p><h4><strong>4. Business Acumen &amp; Decision Mapping</strong></h4><p>Understand the business you&#8217;re supporting&#8212;not just the metrics, but the <strong>decisions those metrics drive</strong>. Ask yourself:</p><ul><li><p>What decision will this analysis influence?</p></li><li><p>Who makes that decision, and what constraints do they face?</p></li><li><p>What&#8217;s the cost of a wrong decision vs a delayed decision?</p></li></ul><p>I&#8217;ve seen brilliant ML models shelved because the analyst didn&#8217;t understand the operational workflow. <strong>Analysts who map their work to decisions get funded, promoted, and trusted.</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EqYV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EqYV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png 424w, https://substackcdn.com/image/fetch/$s_!EqYV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png 848w, https://substackcdn.com/image/fetch/$s_!EqYV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png 1272w, https://substackcdn.com/image/fetch/$s_!EqYV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EqYV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png" width="1009" height="443" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:443,&quot;width&quot;:1009,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:58957,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EqYV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png 424w, https://substackcdn.com/image/fetch/$s_!EqYV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png 848w, https://substackcdn.com/image/fetch/$s_!EqYV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png 1272w, https://substackcdn.com/image/fetch/$s_!EqYV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af88626-ad86-4868-9612-1e14f60e6e29_1009x443.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4><strong>5. Causal Thinking &amp; Experimentation</strong></h4><p>Correlation isn&#8217;t causation&#8212;you know this. But do you know how to <strong>design experiments</strong> to prove causation? Do you understand concepts like:</p><ul><li><p>A/B testing (and when it&#8217;s not appropriate)</p></li><li><p>Difference-in-differences, synthetic controls</p></li><li><p>Confounding variables and how to control for them</p></li></ul><p>In 2026, stakeholders won&#8217;t just ask &#8220;What happened?&#8221; They&#8217;ll ask &#8220;Why did it happen, and what will happen if we change X?&#8221; If you can&#8217;t answer that rigorously, someone else will.</p><h4><strong>6. Ethics &amp; Responsible AI</strong></h4><p>This isn&#8217;t corporate box-checking. It&#8217;s about understanding:</p><ul><li><p>Bias in data and models (and how to mitigate it)</p></li><li><p>Privacy implications (GDPR, data anonymization)</p></li><li><p>Explainability and transparency (can you justify your model&#8217;s decision?)</p></li></ul><p>As AI becomes more embedded in decision-making, <strong>the stakes get higher</strong>. Analytics professionals who can navigate these waters are invaluable.</p><div><hr></div><h3><strong>Layer 3: Influence Architecture (How You Scale Impact)</strong></h3><p>Great analysis that no one uses is worthless. The best analytics professionals I know are <strong>masters of influence</strong>&#8212;they get their work adopted, trusted, and acted upon.</p><h4><strong>7. Storytelling &amp; Visualization</strong></h4><p>Data doesn&#8217;t speak for itself. You need to <strong>craft narratives</strong> that make insights memorable and actionable. This means:</p><ul><li><p>Choosing the right chart (not just defaulting to bar charts)</p></li><li><p>Structuring presentations for executive audiences (bottom line first)</p></li><li><p>Using metaphors and analogies to make complex ideas accessible</p></li></ul><p>If your stakeholders can&#8217;t remember your recommendation 24 hours later, you didn&#8217;t tell the story well enough.</p><h4><strong>8. Stakeholder Management &amp; Influence</strong></h4><p>This is less about &#8220;soft skills&#8221; and more about <strong>strategic relationship-building</strong>:</p><ul><li><p>Identifying champions and skeptics early</p></li><li><p>Managing expectations (underpromise, overdeliver)</p></li><li><p>Navigating organizational politics without getting burned</p></li></ul><p>The best analysts I&#8217;ve worked with don&#8217;t just deliver insights&#8212;they <strong>create pull</strong> for their work by building trust over time.</p><h4><strong>9. Product Thinking</strong></h4><p>Increasingly, analytics work is <strong>productized</strong>&#8212;dashboards, data apps, recommendation engines. You need to think like a product manager:</p><ul><li><p>Who are my users, and what problems do they have?</p></li><li><p>How do I measure success (usage, adoption, impact)?</p></li><li><p>How do I iterate based on feedback?</p></li></ul><p>If you treat every analysis as a one-off deliverable, you&#8217;re missing the opportunity to build <strong>compounding value</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hL4Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hL4Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png 424w, https://substackcdn.com/image/fetch/$s_!hL4Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png 848w, https://substackcdn.com/image/fetch/$s_!hL4Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png 1272w, https://substackcdn.com/image/fetch/$s_!hL4Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hL4Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png" width="820" height="358" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9ea06449-e88e-446e-b854-c057eec585f4_820x358.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:358,&quot;width&quot;:820,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:67903,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hL4Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png 424w, https://substackcdn.com/image/fetch/$s_!hL4Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png 848w, https://substackcdn.com/image/fetch/$s_!hL4Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png 1272w, https://substackcdn.com/image/fetch/$s_!hL4Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ea06449-e88e-446e-b854-c057eec585f4_820x358.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h3><strong>Real-World Experience: The Analyst Who Became Indispensable</strong></h3><p>A few years ago, I worked with a mid-level BI analyst at a manufacturing company. Let&#8217;s call her Sarah. Sarah was technically solid&#8212;good SQL, decent Tableau skills. But she was <strong>stuck</strong>.</p><p>Then Sarah did something smart. She started attending operations meetings. Not to present&#8212;just to listen. She learned the language of the plant floor. She understood <strong>why</strong> OEE (Overall Equipment Effectiveness) mattered, what drove downtime, and what decisions the plant manager wrestled with daily.</p><p>Six months later, Sarah wasn&#8217;t just building dashboards. She was <strong>designing decision workflows</strong>. She mapped out the key decisions (maintenance scheduling, resource allocation), identified the data gaps, and built a system that surfaced actionable recommendations&#8212;not just charts.</p><p>Sarah&#8217;s work went from &#8220;nice to have&#8221; to &#8220;mission-critical.&#8221; She got promoted. Why? Because she mastered <strong>Layers 2 and 3</strong>. She understood context. She influenced outcomes.</p><p>That&#8217;s the career trajectory available to analytics professionals who evolve beyond pure technical execution.</p><div><hr></div><h3><strong>Framework: Your 2026 Skills Roadmap</strong></h3><p>Here&#8217;s how to build this skill stack intentionally:</p><p><strong>Q1-Q2 2026: Audit &amp; Strengthen Layer 1</strong></p><ul><li><p>Get comfortable with LLMs (use GitHub Copilot, ChatGPT for code)</p></li><li><p>Learn one new tool in the modern data stack (dbt, Airflow, Snowflake)</p></li><li><p>Build one small project using cloud-native services</p></li></ul><p><strong>Q3 2026: Develop Layer 2</strong></p><ul><li><p>Shadow a business stakeholder for a week (understand their world)</p></li><li><p>Take a course on causal inference or experimentation</p></li><li><p>Read one business strategy book (I recommend &#8220;Good Strategy/Bad Strategy&#8221;)</p></li></ul><p><strong>Q4 2026: Invest in Layer 3</strong></p><ul><li><p>Practice storytelling: present one analysis to non-technical audiences</p></li><li><p>Build one &#8220;product&#8221; (a recurring dashboard, data app, or automated report)</p></li><li><p>Find a mentor who&#8217;s strong in influence and learn from them</p><p></p></li></ul><p><strong>The key: balance</strong>. Don&#8217;t abandon technical depth. But don&#8217;t neglect the context and influence layers that amplify your impact.</p><div><hr></div><h3><strong>What This Means for BI &#8594; AI Transformation</strong></h3><p>If you&#8217;re in an organization modernizing from BI to AI, here&#8217;s what&#8217;s changing for analytics roles:</p><p><strong>Yesterday&#8217;s BI Analyst:</strong><br>Built reports. Answered &#8220;what happened?&#8221; Used SQL and Excel.</p><p><strong>Today&#8217;s Analytics Professional:</strong><br>Builds decision systems. Answers &#8220;what should we do?&#8221; Uses SQL, Python, ML, and AI tools&#8212;but also understands the business, designs experiments, tells stories, and influences adoption.</p><p>The companies winning the BI &#8594; AI transformation aren&#8217;t just upgrading their tech stack. They&#8217;re <strong>upgrading their talent stack</strong>. They&#8217;re investing in Layer 2 and Layer 3 skills. They&#8217;re turning analysts into <strong>decision architects</strong>.</p><p>If you&#8217;re an analytics leader, the question is: <strong>Are you hiring for the three-layer skill model? Are you developing it in your team?</strong></p><p>If you&#8217;re an individual contributor, the question is: <strong>Which layer needs your attention most right now?</strong></p><div><hr></div><h3><strong>Closing Thought</strong></h3><p>The analytics profession in 2026 will reward <strong>breadth as much as depth</strong>.</p><p>You don&#8217;t need to be world-class at everything. But you need to be <strong>conversationally fluent across all three layers</strong>&#8212;technical craft, contextual intelligence, and influence architecture.</p><p>The good news? These skills are learnable. The better news? Most of your competition isn&#8217;t investing in them yet.</p><p>Start now. Future-you will thank you.</p>]]></content:encoded></item><item><title><![CDATA[Beyond BI and AI: How Decision Intelligence Closes the Last Mile]]></title><description><![CDATA[You've built the dashboards. You've trained the models. But are you actually making better decisions? Here's what comes next in the BI &#8594; AI evolution.]]></description><link>https://ganeshkedari.substack.com/p/beyond-bi-and-ai-how-decision-intelligence</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/beyond-bi-and-ai-how-decision-intelligence</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Fri, 28 Nov 2025 08:18:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1J7n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;ve spent the better part of two decades helping enterprises evolve their data capabilities&#8212;from basic reporting to self-service BI, from predictive models to AI-powered automation. And here&#8217;s what I&#8217;ve learned: <strong>insight doesn&#8217;t equal action.</strong></p><p>You can have the most beautiful Power BI dashboard. You can have a state-of-the-art ML model predicting churn with 94% accuracy. But if your VP of Operations still makes the call based on gut feel in a Monday morning meeting, what have you really achieved?</p><p>This is where <strong>Decision Intelligence (DI)</strong> enters the picture. It&#8217;s not just another buzzword. It&#8217;s the discipline of <strong>engineering better decisions at scale</strong>&#8212;combining data, models, context, and human judgment into repeatable, improvable systems. And if you&#8217;re serious about modernizing your enterprise, this is the next frontier.</p><div><hr></div><p>Let&#8217;s clarify what Decision Intelligence actually is, because the term gets thrown around loosely.</p><p><strong>Business Intelligence</strong> gives you the rearview mirror. It tells you <em>what happened</em>. Revenue last quarter. Customer complaints by region. Inventory levels yesterday.</p><p><strong>Artificial Intelligence</strong> gives you the crystal ball. It tells you <em>what might happen</em> or <em>what pattern exists</em>. Churn predictions. Demand forecasts. Anomaly detection.</p><p><strong>Decision Intelligence</strong> gives you the <strong>steering wheel</strong>. It answers: <em>What should we do about it? And how do we know that&#8217;s the right move?</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DgUO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DgUO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png 424w, https://substackcdn.com/image/fetch/$s_!DgUO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png 848w, https://substackcdn.com/image/fetch/$s_!DgUO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png 1272w, https://substackcdn.com/image/fetch/$s_!DgUO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DgUO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png" width="929" height="809" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:809,&quot;width&quot;:929,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:143499,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DgUO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png 424w, https://substackcdn.com/image/fetch/$s_!DgUO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png 848w, https://substackcdn.com/image/fetch/$s_!DgUO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png 1272w, https://substackcdn.com/image/fetch/$s_!DgUO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F312037f4-2d98-4271-a9d4-de6c2b1bb22b_929x809.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>DI sits at the intersection of:</p><ul><li><p><strong>Data science</strong> (models, predictions, patterns)</p></li><li><p><strong>Decision theory</strong> (how humans and systems choose under uncertainty)</p></li><li><p><strong>Behavioral science</strong> (how decisions are actually made in organizations)</p></li><li><p><strong>Systems thinking</strong> (how decisions ripple across workflows and outcomes)</p></li></ul><div class="pullquote"><p>It&#8217;s not about replacing human judgment. It&#8217;s about <strong>augmenting it with structure, evidence, and feedback loops</strong>.</p></div><p>A few years ago, I worked with a mid-sized pharma company on their supply chain optimization. They had great BI dashboards. They had an ML model predicting demand at the SKU level. The model was accurate. The dashboards were clean.</p><p>But every Monday, the supply chain team would meet, look at the recommendations, and then&#8230; override them. Why? Because the model didn&#8217;t account for promotional calendars. It didn&#8217;t know about an upcoming plant shutdown. It didn&#8217;t reflect the reality that certain distributors were more strategic than others.</p><p>The model was technically correct. But it wasn&#8217;t <em>decision ready</em>.</p><p>So, we redesigned the system as a <strong>Decision Intelligence platform</strong>. We didn&#8217;t just show predictions. We surfaced:</p><ul><li><p><strong>Recommended actions</strong> (with reasoning)</p></li><li><p><strong>Trade-offs</strong> (cost vs service level)</p></li><li><p><strong>Constraints</strong> (regulatory, capacity, strategic priorities)</p></li><li><p><strong>What-if scenarios</strong> (if we do X, here&#8217;s the expected outcome)</p></li><li><p><strong>Feedback mechanisms</strong> (track what happened when we followed or ignored the recommendation)</p></li></ul><p>Within six months, adherence to model-driven decisions went from 30% to 85%. Not because we had a better model. Because we had a better <strong>decision architecture</strong>.</p><p></p><div><hr></div><h3><strong>Framework: The 5 Pillars of Decision Intelligence</strong></h3><p>If you&#8217;re building Decision Intelligence capability in your organization, here&#8217;s the framework I use:</p><h4><strong>1. Decision Modeling</strong></h4><p>Map the decisions that matter. Not every decision need DI. Focus on <strong>high-frequency, high-impact decisions</strong> that are repeatable. Examples: pricing adjustments, inventory reorders, patient treatment pathways, resource allocation.</p><h4><strong>2. Context Integration</strong></h4><p>Pull in everything that influences the decision&#8212;not just historical data. That means:</p><ul><li><p>Business rules and constraints</p></li><li><p>Strategic priorities</p></li><li><p>External signals (market conditions, weather, competitor moves)</p></li><li><p>Human expertise and tacit knowledge</p></li></ul><h4><strong>3. Action-Oriented Outputs</strong></h4><p>Don&#8217;t just predict. <strong>Prescribe</strong>. Show the recommended action, the alternatives, and the expected outcomes. Make it <strong>easy to act</strong> and <strong>hard to ignore</strong>.</p><h4><strong>4. Human-in-the-Loop Design</strong></h4><p>DI isn&#8217;t full automation. It&#8217;s <strong>collaborative intelligence</strong>. Design for:</p><ul><li><p>Override capability (humans can disagree and explain why)</p></li><li><p>Contextual overrides (e.g., &#8220;this customer is strategic, don&#8217;t churn them&#8221;)</p></li><li><p>Escalation workflows (complex cases go to experts)</p></li></ul><h4><strong>5. Feedback &amp; Learning</strong></h4><p>Track outcomes. Did the decision work? Feed that back into the model. <strong>Decision Intelligence gets smarter over time</strong> because it learns from its own performance.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Oqdq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Oqdq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png 424w, https://substackcdn.com/image/fetch/$s_!Oqdq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png 848w, https://substackcdn.com/image/fetch/$s_!Oqdq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png 1272w, https://substackcdn.com/image/fetch/$s_!Oqdq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Oqdq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png" width="728" height="503.0829694323144" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:633,&quot;width&quot;:916,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:148990,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Oqdq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png 424w, https://substackcdn.com/image/fetch/$s_!Oqdq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png 848w, https://substackcdn.com/image/fetch/$s_!Oqdq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png 1272w, https://substackcdn.com/image/fetch/$s_!Oqdq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1722ace-aa06-46e8-a7ad-8377727552b8_916x633.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Let&#8217;s say you&#8217;re a retail chain deciding <strong>when to mark down slow-moving inventory</strong>.</p><p><strong>Traditional BI approach:</strong><br>Dashboard shows: &#8220;Product X has 45 days of inventory. Category average is 30 days.&#8221;<br><strong>Action:</strong> Manager decides to markdown, or not, based on experience.</p><p><strong>AI approach:</strong><br>Model predicts: &#8220;Product X has 78% chance of clearance if marked down 20% this week.&#8221;<br><strong>Action:</strong> Manager still has to interpret and decide.</p><p><strong>Decision Intelligence approach:</strong><br>System recommends: &#8220;Mark down Product X by 25% starting Thursday. Expected margin impact: -$1,200. Expected sell-through improvement: +40%. Alternative: wait one week, but risk increases to needing 40% markdown. Override option available if product is in active promotion.&#8221;<br><strong>Action:</strong> Clear, contextualized, actionable. Manager can approve, adjust, or override with reasoning.</p><p>The difference? <strong>DI doesn&#8217;t make you smarter. It makes your decision-making process faster, more consistent, and improvable.</strong></p><div><hr></div><h3><strong>What This Means for BI &#8594; AI Transformation</strong></h3><p>If you&#8217;re leading a data or analytics team, you&#8217;ve probably been on this journey:</p><p><strong>Phase 1:</strong> Build dashboards (BI)<br><strong>Phase 2:</strong> Automate reporting (self-service BI)<br><strong>Phase 3:</strong> Build predictive models (AI/ML)<br><strong>Phase 4:</strong> Productionize models (MLOps)</p><p>And now you&#8217;re stuck. You have models in production. You have insights flowing. But <strong>adoption is inconsistent</strong>. Business teams still &#8220;trust their gut.&#8221; Executives ask, &#8220;What should we do?&#8221; and your answer is, &#8220;Here&#8217;s what the data says,&#8221; followed by awkward silence.</p><p><strong>Decision Intelligence is Phase 5.</strong></p><p>It&#8217;s the layer that <strong>closes the gap between insight and action</strong>. It takes your BI foundation and your AI capabilities and wraps them in a decision-making framework that the business can actually use.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1J7n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1J7n!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png 424w, https://substackcdn.com/image/fetch/$s_!1J7n!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png 848w, https://substackcdn.com/image/fetch/$s_!1J7n!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png 1272w, https://substackcdn.com/image/fetch/$s_!1J7n!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1J7n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png" width="850" height="624" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:624,&quot;width&quot;:850,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:137965,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1J7n!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png 424w, https://substackcdn.com/image/fetch/$s_!1J7n!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png 848w, https://substackcdn.com/image/fetch/$s_!1J7n!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png 1272w, https://substackcdn.com/image/fetch/$s_!1J7n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62b9cc9a-e8db-4b1e-82d6-c8c63a0f33d7_850x624.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p>Here&#8217;s what changes:</p><ul><li><p>Your data scientists stop being &#8220;model builders&#8221; and start being <strong>decision architects</strong>.</p></li><li><p>Your BI dashboards evolve from &#8220;here&#8217;s what happened&#8221; to &#8220;here&#8217;s what to do about it.&#8221;</p></li><li><p>Your models shift from <strong>accuracy metrics</strong> to <strong>decision quality metrics</strong> (did we make better choices?).</p></li><li><p>Your governance shifts from &#8220;data quality&#8221; to <strong>&#8220;decision quality&#8221;</strong> (are we learning from outcomes?).</p></li></ul><p>This is the natural evolution. And if you&#8217;re not thinking about it yet, your competitors&#8212;or your leadership team&#8212;will be soon.</p><div><hr></div><h3><strong>Closing Thought</strong></h3><p>Decision Intelligence isn&#8217;t about having perfect information. It&#8217;s about <strong>making the best possible decision with imperfect information and getting better at it over time</strong>.</p><p>We&#8217;ve spent the last 20 years building systems that help us <em>see</em> better. Now it&#8217;s time to build systems that help us <em>choose</em> better.</p><p>Because at the end of the day, enterprises don&#8217;t win on insights. They win on decisions.</p>]]></content:encoded></item><item><title><![CDATA[Dashboards vs Dialogue: Why the Future of Analytics Is Not Visual]]></title><description><![CDATA[Why the next era of analytics won&#8217;t be visual &#8212; it will be intelligent, contextual, and conversational.]]></description><link>https://ganeshkedari.substack.com/p/dashboards-vs-dialogue-why-the-future</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/dashboards-vs-dialogue-why-the-future</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Tue, 25 Nov 2025 06:35:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!SYxZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1></h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SYxZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SYxZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png 424w, https://substackcdn.com/image/fetch/$s_!SYxZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png 848w, https://substackcdn.com/image/fetch/$s_!SYxZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png 1272w, https://substackcdn.com/image/fetch/$s_!SYxZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SYxZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png" width="3702" height="1291" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1291,&quot;width&quot;:3702,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4208262,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/179893779?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37891041-1108-4234-be6a-f141c2746313_3780x1890.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SYxZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png 424w, https://substackcdn.com/image/fetch/$s_!SYxZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png 848w, https://substackcdn.com/image/fetch/$s_!SYxZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png 1272w, https://substackcdn.com/image/fetch/$s_!SYxZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f48d7d1-460b-4c37-8b45-8821778f1f67_3702x1291.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For years, dashboards were the center of the analytics world - Beautiful layouts. Colorful charts. Interactive filters. But here&#8217;s the truth most teams don&#8217;t admit: <strong>Dashboards only work if you already know exactly what you&#8217;re looking for.</strong></p><p>That&#8217;s the flaw. And that&#8217;s why the analytics future won&#8217;t be visual &#8212; it will be conversational.</p><div><hr></div><h2><strong>Dashboards Show You Data. Conversation Shows You Direction.</strong></h2><p>Dashboards summarize history. They&#8217;re great when you want static KPIs or high-level visibility. But ask leaders what they really struggle with, and the same answers repeat:</p><ul><li><p>&#8220;Where do I click?&#8221;</p></li><li><p>&#8220;Which report is the latest?&#8221;</p></li><li><p>&#8220;Why doesn&#8217;t this tell me what to do next?&#8221;</p></li></ul><div class="pullquote"><p><strong>Dashboards end at </strong><em><strong>insight</strong></em><strong>. Dialogue begins at </strong><em><strong>action</strong></em><strong>.</strong></p></div><div><hr></div><h2></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FomE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FomE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png 424w, https://substackcdn.com/image/fetch/$s_!FomE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png 848w, https://substackcdn.com/image/fetch/$s_!FomE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png 1272w, https://substackcdn.com/image/fetch/$s_!FomE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FomE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png" width="868" height="423" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:423,&quot;width&quot;:868,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:83004,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/179893779?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b64a81b-8c5c-4602-8534-736d5f6eafdf_888x528.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FomE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png 424w, https://substackcdn.com/image/fetch/$s_!FomE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png 848w, https://substackcdn.com/image/fetch/$s_!FomE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png 1272w, https://substackcdn.com/image/fetch/$s_!FomE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fb38281-e267-4bc3-88e4-140d97ed6e10_868x423.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3><strong>Dashboards (The Old Way)</strong></h3><ul><li><p>Static charts</p></li><li><p>Manual drill-downs</p></li><li><p>Clicking, guessing, filtering</p></li><li><p>Everything is reactive</p></li></ul><h3><strong>Dialogue (The New Way)</strong></h3><ul><li><p>&#8220;Why did revenue drop?&#8221;</p></li><li><p>&#8220;What should I focus on this week?&#8221;</p></li><li><p>&#8220;Show me anomalies and notify my team.&#8221;</p></li><li><p>Answers that come to you &#8212; not the other way around</p></li></ul><div class="pullquote"><p><strong>Dashboards demand your effort. Conversation adapts to your intent.</strong></p></div><div><hr></div><h2><strong>Why Conversational Analytics Is Surpassing BI</strong></h2><h3><strong>1. No friction</strong></h3><p>You don&#8217;t need to learn 20 dashboards. You just ask.</p><h3><strong>2. Context-aware answers</strong></h3><p>It remembers your business, your past questions, your goals.</p><h3><strong>3. Action-oriented</strong></h3><p>You can go from question &#8594; insight &#8594; next step in one interaction. Dashboards can&#8217;t do that. Dialogue can.</p><div><hr></div><h2><strong>The Future Is Already Here</strong></h2><p>Most organizations are still dashboard-heavy today. But the winning ones?<br>They&#8217;re moving toward a world where:</p><ul><li><p>Dashboards become thinner</p></li><li><p>Conversational layers become primary</p></li><li><p>Data begins to <em>recommend</em> instead of merely <em>report</em></p></li></ul><p>We&#8217;re shifting from <strong>visual analytics</strong> to <strong>intelligent dialogue</strong>.</p><div><hr></div><h2><strong>Final Thought</strong></h2><p>The companies that win won&#8217;t be the ones with the most dashboards.<br>They&#8217;ll be the ones where people <strong>talk to data the way they talk to teammates</strong> &#8212; naturally, instantly, and without friction.</p>]]></content:encoded></item><item><title><![CDATA[Stop Writing Prompts: A Guide to DSPy and the Future of LLM Programming]]></title><description><![CDATA[Why manual prompt engineering is a dead end, and how to build self-optimizing pipelines with DSPy and TGI]]></description><link>https://ganeshkedari.substack.com/p/stop-writing-prompts-a-guide-to-dspy</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/stop-writing-prompts-a-guide-to-dspy</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Thu, 20 Nov 2025 09:03:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!pyZY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are building with Large Language Models (LLMs) today, you are likely stuck in the &#8220;Prompt Engineering Loop of Despair.&#8221; It looks something like this:</p><ol><li><p>You write a 50-line prompt to get an LLM to extract sentiment.</p></li><li><p>It works great on 5 examples.</p></li><li><p>It fails on the 6th.</p></li><li><p>You add a &#8220;Do not hallucinate&#8221; clause and capitalization: &#8220;IMPORTANT: ONLY OUTPUT JSON.&#8221;</p></li><li><p>It works on the 6th example but breaks the first 5.</p></li><li><p>You upgrade from Llama-2 to Llama-3, and now you have to rewrite everything.</p></li></ol><p>This is <strong>brittle</strong>, <strong>unscalable</strong>, and frankly, <strong>unscientific</strong>.</p><p>Enter <strong><a href="https://dspy.ai/">DSPy</a></strong><a href="https://dspy.ai/"> </a>(Declarative Self-improving Python), a framework from Stanford NLP that proposes a radical shift: <strong>Stop writing prompts. Start programming.</strong></p><p>In this article, we will dismantle the prompt engineering mindset and walk through a real-world example using <strong><a href="https://huggingface.co/docs/text-generation-inference/en/index">Hugging Face TGI</a></strong>, creating a sentiment analyzer that optimizes its own prompts.</p><div><hr></div><h2>The Paradigm Shift: From Strings to Signatures</h2><p>The core philosophy of DSPy is that <strong>prompts are effectively the &#8220;weights&#8221; of an LLM application.</strong></p><p>In traditional machine learning (like PyTorch), you don&#8217;t manually tweak the weights of a neuron to make it recognize a cat. You define the architecture, provide data, define a loss function, and let an optimizer (like SGD) find the weights for you.</p><p>DSPy applies this exact logic to LLMs:</p><ul><li><p><strong>Manual approach:</strong> You fiddle with the string &#8220;You are a helpful assistant...&#8221; until it works.</p></li><li><p><strong>DSPy approach:</strong> You define <strong>what</strong> you want (Input $\to$ Output) and give it examples. DSPy acts as the &#8220;optimizer&#8221; and figures out the best prompt to achieve that result.</p></li></ul><p>If you change the model (e.g., switching from OpenAI to a local model on TGI), you don&#8217;t rewrite prompts. You just &#8220;recompile&#8221; your program.</p><div><hr></div><h2>The Three Pillars of DSPy</h2><p>To understand DSPy, you need to understand its three main abstractions.</p><h3>1. Signatures (The Interface)</h3><p>A <strong>Signature</strong> is a declarative specification of input and output. It cares about <em>what</em> the transformation is, not <em>how</em> to prompt for it.</p><h3>2. Modules (The Architecture)</h3><p>A <strong>Module</strong> uses Signatures to perform work. These are building blocks like <code>dspy.ChainOfThought</code> or <code>dspy.ReAct</code>.</p><h3>3. Optimizers (The &#8220;Magic&#8221;)</h3><p><strong>Optimizers</strong> take your program, a training dataset, and a metric, and they <strong>tune</strong> the prompts by generating synthetic few-shot examples or instructions that maximize your metric.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pyZY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pyZY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png 424w, https://substackcdn.com/image/fetch/$s_!pyZY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png 848w, https://substackcdn.com/image/fetch/$s_!pyZY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png 1272w, https://substackcdn.com/image/fetch/$s_!pyZY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pyZY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png" width="602" height="532" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:532,&quot;width&quot;:602,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82714,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ganeshkedari.substack.com/i/179436791?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde5e2406-4290-474d-be7d-695f188de63b_612x612.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pyZY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png 424w, https://substackcdn.com/image/fetch/$s_!pyZY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png 848w, https://substackcdn.com/image/fetch/$s_!pyZY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png 1272w, https://substackcdn.com/image/fetch/$s_!pyZY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f8c8a0d-9f6b-4b2f-983f-ce40e4ade627_602x532.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h2>Hands-On Lab: Sentiment Analysis with TGI</h2><p>Let&#8217;s get technical. We aren&#8217;t using OpenAI here; we are going to use a self-hosted model via <strong>Hugging Face Text Generation Inference (TGI)</strong>. This is a common setup for enterprises requiring data privacy or low latency.</p><p><strong>Scenario:</strong> You have a TGI server running <code>meta-llama/Meta-Llama-3-8B-Instruct</code>. You want to build a robust sentiment analyzer.</p><h3>Step 1: Connect DSPy to TGI</h3><p>First, we tell DSPy to use your TGI endpoint as the Language Model (LM).</p><pre><code><code>import dspy

# Connect to your local or remote TGI instance
# TGI must be running on the specified port (e.g., 8080)
tgi_lm = dspy.HFClientTGI(
    target=&#8221;http://127.0.0.1:8080&#8221;, 
    model=&#8221;meta-llama/Meta-Llama-3-8B-Instruct&#8221;,
    max_tokens=200
)

# Configure DSPy to use this as the default
dspy.settings.configure(lm=tgi_lm)
</code></code></pre><h3>Step 2: Define the Signature</h3><p>We don&#8217;t write a prompt saying &#8220;You are a sentiment classifier.&#8221; We just define the inputs and outputs.</p><pre><code><code>class SentimentSignature(dspy.Signature):
    &#8220;&#8221;&#8220;
    Analyze the sentiment of the input text. 
    Output must be exactly one word: Positive, Negative, or Neutral.
    &#8220;&#8221;&#8220;
    sentence = dspy.InputField(desc=&#8221;The customer review or comment&#8221;)
    sentiment = dspy.OutputField(desc=&#8221;The resulting sentiment (Positive, Negative, Neutral)&#8221;)
</code></code></pre><h3>Step 3: Prepare &#8220;Training&#8221; Data</h3><p>DSPy needs a few examples to learn from. We create a small list of <code>dspy.Example</code> objects.</p><pre><code><code>train_data = [
    dspy.Example(sentence=&#8221;This product is absolute garbage.&#8221;, sentiment=&#8221;Negative&#8221;).with_inputs(&#8221;sentence&#8221;),
    dspy.Example(sentence=&#8221;I really loved the UI, very intuitive.&#8221;, sentiment=&#8221;Positive&#8221;).with_inputs(&#8221;sentence&#8221;),
    dspy.Example(sentence=&#8221;It arrived on time, but the packaging was dented.&#8221;, sentiment=&#8221;Neutral&#8221;).with_inputs(&#8221;sentence&#8221;),
    dspy.Example(sentence=&#8221;Best purchase I&#8217;ve made all year!&#8221;, sentiment=&#8221;Positive&#8221;).with_inputs(&#8221;sentence&#8221;),
    dspy.Example(sentence=&#8221;I wouldn&#8217;t recommend this to my worst enemy.&#8221;, sentiment=&#8221;Negative&#8221;).with_inputs(&#8221;sentence&#8221;)
]
</code></code></pre><h3>Step 4: Define the Module</h3><p>We will use <code>ChainOfThought</code>. This forces the model to reason before answering, which typically increases accuracy for ambiguous sentences.</p><pre><code><code>class SentimentAnalyzer(dspy.Module):
    def __init__(self):
        super().__init__()
        # Using ChainOfThought wraps our signature with a &#8220;reasoning&#8221; step
        self.prog = dspy.ChainOfThought(SentimentSignature)
    
    def forward(self, sentence):
        return self.prog(sentence=sentence)
</code></code></pre><h3>Step 5: The Optimization (Compiling)</h3><p>This is where the magic happens. We will use the <code>BootstrapFewShot</code> optimizer.</p><p><strong>How it works:</strong></p><ol><li><p>It takes a teacher model (usually the same as the student, but can be a stronger one).</p></li><li><p>It runs your <code>train_data</code> through your <code>SentimentAnalyzer</code>.</p></li><li><p>If the model outputs the correct sentiment (based on our metric), DSPy saves the <em>reasoning trace</em> that led to that correct answer.</p></li><li><p>It compiles a new prompt that includes these &#8220;perfect reasoning steps&#8221; as few-shot examples.</p></li></ol><pre><code><code>from dspy.teleprompt import BootstrapFewShot

# 1. Define a Metric: How do we know if the model is right?
def sentiment_metric(gold, pred, trace=None):
    # Check if the predicted sentiment matches the ground truth (gold)
    return pred.sentiment.lower() == gold.sentiment.lower()

# 2. Initialize the Optimizer
optimizer = BootstrapFewShot(metric=sentiment_metric, max_bootstrapped_demos=4)

# 3. Compile!
# DSPy runs the training data against TGI and learns how to prompt it effectively.
compiled_analyzer = optimizer.compile(student=SentimentAnalyzer(), trainset=train_data)
</code></code></pre><h3>Step 6: Run Inference</h3><p>Now we use the <code>compiled_analyzer</code>. It isn&#8217;t just a zero-shot prompt anymore; it is a program carrying the &#8220;weights&#8221; (demonstrations) it learned during compilation.</p><pre><code><code># Test on unseen data
test_sentence = &#8220;The battery life is mediocre, but the screen is gorgeous.&#8221;

response = compiled_analyzer(test_sentence)

print(f&#8221;Reasoning: {response.reasoning}&#8221;)
print(f&#8221;Sentiment: {response.sentiment}&#8221;)
</code></code></pre><p>What just happened?</p><p>Because we used ChainOfThought and optimized it, the output from TGI will look something like this:</p><blockquote><p>Reasoning: The user mentions &#8220;mediocre battery life&#8221; which is negative, but contrasts it with a &#8220;gorgeous screen&#8221; which is positive. The &#8220;but&#8221; implies a mixed feeling, but often features like screen quality weigh heavily. However, generally, this balance suggests a Neutral or slightly mixed stance. Let&#8217;s classify as Neutral based on the balance.</p><p>Sentiment: Neutral</p></blockquote><p>The model taught itself <em>how</em> to think about your specific data distribution by looking at the training examples you provided.</p><div><hr></div><h2>DSPy vs. LangChain vs. LlamaIndex</h2><p>This is the most common confusion.</p><ul><li><p><strong>LangChain/LlamaIndex</strong> are <strong>Orchestration Frameworks</strong>. They are great at &#8220;plumbing&#8221;&#8212;connecting PDFs to vector databases, managing chat history, and gluing APIs together. However, inside that glue, you are usually still pasting raw strings: <code>PromptTemplate(&#8221;You are a helpful assistant...&#8221;)</code>.</p></li><li><p><strong>DSPy</strong> is an <strong>Optimization Framework</strong>. It doesn&#8217;t care about your PDF parser. It cares about maximizing the accuracy of the text flowing through your system.</p></li></ul><p><strong>The Power Move:</strong> You can use them together. Use LlamaIndex to load your data and handle the retrieval index, but use DSPy to orchestrate the logic and prompt optimization of the answering engine.</p><div><hr></div><h2>Why You Should Care (The ROI)</h2><h3>1. Systematized Improvement</h3><p>If your boss asks, &#8220;How do we increase accuracy from 60% to 70%?&#8221;, the non-DSPy answer is &#8220;I&#8217;ll try to rewrite the prompt.&#8221; The DSPy answer is &#8220;I will add 50 more labelled examples to the training set and re-compile with MIPRO.&#8221; One is art; the other is engineering.</p><h3>2. Model Agnosticism</h3><p>If you optimize a complex pipeline for GPT-4, it usually breaks on a smaller model like Llama-3-8b. With DSPy, you simply swap the LLM definition and re-compile. The optimizer will likely find that the smaller model needs <em>more</em> examples or <em>simpler</em> reasoning steps, and it will adjust the prompt accordingly&#8212;automatically.</p><h3>3. Debugging</h3><p>DSPy allows you to inspect &#8220;traces.&#8221; You can see exactly which step in your chain failed, not just the final output.</p><h2>Conclusion</h2><p>We are moving away from the era of the &#8220;AI Whisperer&#8221; who knows the magic words to make the model behave. We are moving toward the era of <strong>LM Ops</strong>, where prompts are compiled artifacts derived from data.</p><p>DSPy is currently the leading framework driving this shift. It turns the &#8220;art&#8221; of prompting into a repeatable, measurable, and optimizable science.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ganeshkedari.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Geeky Tales! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[When Data Starts Talking: The Rise of Conversational Analytics]]></title><description><![CDATA[What happens when your data becomes a colleague instead of a dashboard? A deep dive into the rise of conversational analytics and the future of intelligent decision-making.]]></description><link>https://ganeshkedari.substack.com/p/when-data-starts-talking-the-rise</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/when-data-starts-talking-the-rise</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Mon, 17 Nov 2025 14:20:38 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fd25ebba-c1c4-4eb0-8083-833be076e857_1024x254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We used to treat analytics like a museum. Curated dashboards behind glass. Executives and analysts came to view them on occasion, then left.</p><p>Conversational analytics changes that. It turns analytics into a colleague you can speak with, query in plain language, and get context-rich answers from in seconds. This isn&#8217;t novelty UI. It&#8217;s a practical shift that lowers the barrier to insight, speeds decision cycles, and changes who can ask and act on data.</p><p>Below I map the why, the how, the measurable benefits, and the real tradeoffs. This comes from 16 years building these systems for life sciences and enterprise clients, watching what works and what doesn&#8217;t.</p><h2>Why conversational analytics matters now</h2><p>Three forces make this more than a gimmick:</p><p><strong>Data democratization demand.</strong> Business users want answers without waiting days for analysts. Natural language interfaces remove SQL or BI-tool fluency as blockers.</p><p><strong>Mature NLP and retrieval tech.</strong> Advances in embeddings, transformer models, and semantic search let systems understand intent. They surface relevant facts, not just keyword matches.</p><p><strong>Operational urgency.</strong> Real-time decisions (supply chain reroutes, sales actions, clinical ops) benefit from immediate, conversational responses. Static dashboards can&#8217;t keep up.</p><p>The result? Conversational analytics shifts from pull to push/pull. Users don&#8217;t just open dashboards. They ask questions, get clarifying prompts, and receive recommendations. Different UX. Different organizational model.</p><h2>What conversational analytics actually delivers</h2><p>From projects I&#8217;ve led across pharma, supply chain, and manufacturing, the practical outcomes are tangible:</p><p><strong>Faster time to insight.</strong> Business users get answers in minutes rather than hours or days. In deployments I&#8217;ve observed, this can reduce decision latency by roughly 30% for common use cases like ad-hoc queries and anomaly investigation. Your mileage may vary based on data maturity and organizational readiness.</p><p><strong>Higher adoption of analytics.</strong> Conversational access reduces the analytics help desk backlog. Self-service adoption increases among non-technical roles because the interface meets them where they are.</p><p><strong>Contextual decisioning.</strong> Beyond charts, systems can return recommendations. &#8220;Re-route 200 units from DC-A to DC-B to avoid 48-hour delay&#8221; with confidence scores. That&#8217;s actionable, not just informational.</p><p><strong>Operational automation hooks.</strong> Answers can trigger workflows: alerts, tickets, automated model runs. This closes the loop between insight and action.</p><p>Those are the opportunities. Now let&#8217;s look at how to get there.</p><h2>Core components of a conversational analytics system</h2><p>A production-ready system is more than an NLP endpoint. Here&#8217;s what you need at a high level:</p><h3>Unified semantic layer and data catalog</h3><p><strong>Purpose:</strong> Translate business language to canonical data entities. &#8220;Prescribers&#8221; becomes <code>prescriber_id</code>.</p><p><strong>Why it matters:</strong> Prevents hallucinations and inconsistent answers across queries. If your semantic layer is weak, your conversational system will make stuff up.</p><h3>Intent detection and query parser</h3><p><strong>Purpose:</strong> Detect what the user wants (insight, explanation, forecast, action) and extract entities, timeframes, filters.</p><p><strong>Example:</strong> User asks &#8220;Which regions will miss target next month?&#8221; The system identifies intent as prediction/forecast, scope as regions, timeframe as next month.</p><h3>Analytics runtime and execution engine</h3><p><strong>Purpose:</strong> Execute queries via SQL, analytic pipelines, ML model inference, or hybrid approaches (semantic SQL plus model outputs).</p><p><strong>Note:</strong> Guardrails should decide whether to run cached reports, launch model inference, or run live queries. You don&#8217;t want every question hitting production databases.</p><h3>Conversational layer and dialog manager</h3><p><strong>Purpose:</strong> Manage multi-turn conversations, clarifications, follow-ups, context.</p><p><strong>Example flow:</strong> User asks &#8220;Why are sales down?&#8221; System asks, &#8220;Which product line and timeframe?&#8221; User clarifies. System provides root-cause signals.</p><h3>Explanation and provenance module</h3><p><strong>Purpose:</strong> Show where answers came from. Data sources, models, assumptions. Provide confidence and limitations.</p><p><strong>Why it matters:</strong> Builds trust. Supports auditability. Senior engineers and business stakeholders won&#8217;t trust a black box.</p><h3>Action integration (automation hooks)</h3><p><strong>Purpose:</strong> Allow conversational outputs to trigger actions. Alerts, emails, system updates, runbooks.</p><h3>Governance and monitoring</h3><p><strong>Purpose:</strong> Track query patterns, accuracy, model drift, audit conversations for compliance and retraining signals.</p><h2>Implementation patterns and practical choices</h2><p>There are two practical ways to design the execution layer:</p><p><strong>Semantic SQL-first:</strong> Map natural language to SQL via a semantic layer. Good for well-modeled, governed data. Fast, more deterministic, easier to trace.</p><p><strong>Hybrid ML plus retrieval:</strong> Use embeddings and models to retrieve answers from documents, reports, unstructured data. Combine with live SQL for structured facts. Better for mixed data sources (reports, docs, dashboards).</p><p>In many enterprise settings, hybrid wins. Use SQL and governed tables for core KPIs. Use semantic retrieval for documents, analyst notes, and downstream explanations.</p><h2>Tech stack choices (what I use and why)</h2><p>Here&#8217;s what works in production:</p><p><strong>Semantic layer:</strong> Proprietary or open-source. Data catalogs with business glossaries.</p><p><strong>NLP and embeddings:</strong> Transformer-based encoders for semantic search. Vector databases for retrieval.</p><p><strong>Execution:</strong> BI engine (Power BI, Cognos), query service (Snowflake, Athena), or analytics microservices for model inference.</p><p><strong>Orchestration and CI/CD:</strong> MLOps pipelines, containerized inference behind API gateways, CI for SQL and model tests.</p><p><strong>Observability:</strong> Grafana plus Prometheus for latency. Custom dashboards for correctness and usage.</p><h2>UX and trust: the non-technical half that determines success</h2><p>Technical accuracy is necessary but not sufficient. These human-centered practices determine adoption:</p><p><strong>Show provenance.</strong> Always show data sources, timestamps, confidence scores. Users should know why an answer is returned.</p><p><strong>Clarify assumptions.</strong> For forecasts, surface the model version, training window, key drivers.</p><p><strong>Design for follow-ups.</strong> Allow users to ask &#8220;Why?&#8221; or &#8220;Show me the underlying table.&#8221; Multi-turn is key.</p><p><strong>Limit scope gradually.</strong> Start with a small set of high-value intents. &#8220;Forecast next 90 days,&#8221; &#8220;explain top drivers.&#8221; Expand after you prove value.</p><p><strong>Provide safe fallbacks.</strong> If the system is low-confidence, offer to create a ticket or prompt an analyst. Don&#8217;t hallucinate.</p><h2>Governance, legal, and ethical considerations</h2><p>Conversational analytics amplifies both value and risk:</p><p><strong>Hallucinations.</strong> LLM-like components can produce plausible but incorrect answers. Mitigate with retrieval-based grounding and deterministic SQL when needed.</p><p><strong>PII and compliance.</strong> Conversations can expose PHI or PII. Enforce role-based access, redact sensitive fields, log queries for audits.</p><p><strong>Change management.</strong> Align with legal and compliance teams early. Logged conversations are often discoverable artifacts in legal contexts.</p><h2>Measuring success: KPIs to track</h2><p>Track both usage and quality:</p><p><strong>Adoption:</strong> Percentage of users interacting via conversation versus dashboards.</p><p><strong>Resolution velocity:</strong> Average time from question to actionable outcome.</p><p><strong>Accuracy and trust:</strong> Percentage of high-confidence answers validated by SMEs.</p><p><strong>Operational impact:</strong> Reduction in manual ticket volume, reduction in decision latency, or lift in KPI (for example, churn reduction).</p><p><strong>Model health:</strong> Drift rates, retraining frequency, feedback loops.</p><h2>Final thoughts: conversational analytics is a human problem with tech solutions</h2><p>When data starts talking, the profound change is social as much as technical. You&#8217;re changing who can ask questions, how quickly decisions are made, and how organizations allocate analytic talent.</p><p>The highest-return projects don&#8217;t only automate answers. They rewire workflows so that insights directly become actions.</p><p>Start small. Focus on value. Measure impact. And always make the conversational system explain itself, because trust is the currency of any AI that speaks.</p>]]></content:encoded></item><item><title><![CDATA[From BI to AI: The Evolution Every Data-Driven Business Must Embrace]]></title><description><![CDATA[By Ganesh Kedari &#8212; Solution Architect, Data & AI Transformation]]></description><link>https://ganeshkedari.substack.com/p/from-bi-to-ai-the-evolution-every</link><guid isPermaLink="false">https://ganeshkedari.substack.com/p/from-bi-to-ai-the-evolution-every</guid><dc:creator><![CDATA[Ganesh Kedari]]></dc:creator><pubDate>Fri, 14 Nov 2025 08:49:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5f3d3e8b-5936-4334-8730-e81a57fc6a89_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We&#8217;ve really come a long way from those old-school dashboards and it almost feels like another era. Back then, if your company had a couple of neat charts tracking KPIs, you got to call yourself &#8220;data-driven&#8221; and everyone was onboarded. </p><p>Today, though, those same dashboards feel a bit like glancing in a rearview mirror whilst the world is speeding ahead. Being data-driven now isn&#8217;t about observing; it&#8217;s about anticipating, nudging, and sometimes letting the system think a bit for you.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ganeshkedari.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Geeky Tales! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>That&#8217;s basically where the shift from BI to modern AI begins. And honestly, it&#8217;s less a technology upgrade and more a mindset shift-like switching from thinking in snapshots to thinking in streams.</p><h3>The BI Era: Mostly Looking Back</h3><p>BI was our first real attempt at making sense of the growing pile of operational data. It gave us structure, it gave leaders visibility, and for the first time we could answer basic essential things as :</p><ul><li><p>What was the sales for last quarter?</p></li><li><p>Which teams overspent?</p></li><li><p>Where did we fall short?</p></li></ul><p>Useful questions, sure, but all grounded firmly in the past. BI rarely told us why something happened, and it definitely didn&#8217;t whisper anything about what was coming next. It was a solid toolset, just fundamentally reactive.</p><h3>The Data Surge That Changed the Game</h3><p>Then the floodgates opened-cloud data platforms matured, compute got cheap, and frameworks for ML stopped feeling like science experiments. Tools like Snowflake, Redshift, Azure Synapse made hoarding data almost too easy. Suddenly organizations weren&#8217;t wrestling for information; they had more than they knew what to do with.</p><p>But the bridge from BI to AI wasn&#8217;t just &#8220;more data + more compute.&#8221; What was missing, and still is in many places, is the architecture to support intelligent systems and the culture that&#8217;s willing to use them.</p><h3>The AI Era: From Insight to Actual Guidance</h3><p>AI doesn&#8217;t just hand you a chart; it leans in and says, &#8220;By the way, here&#8217;s what&#8217;s around the corner and here&#8217;s what you might want to do about it.&#8221;</p><p>Think about a sales leader asking which accounts might churn and getting an answer immediately. Or a supply chain analyst checking next week&#8217;s weak spots and being offered rerouting options before they even finish their coffee. Even executives can now type a casual query like &#8220;summarize Q3 drivers&#8221; and get a coherent breakdown in seconds.</p><p>Conversational analytics-tools like <a href="http://whiz.ai">WhizAI</a>, IBM Watson, and others-have basically cracked open the gates. Suddenly, analytics isn&#8217;t a walled garden guarded by data scientists; everyone can play.</p><h3>The Move from BI &#8594; AI: A Rough Map</h3><p>For teams still anchored in BI mode, the shift doesn&#8217;t happen in one big leap. It&#8217;s more like tuning different parts of a system:</p><ul><li><p>Modernize the architecture. <br>Multi-cloud, unified data across SAP, Snowflake, Salesforce&#8230; the whole ecosystem.</p></li><li><p>Build pipelines that expect AI. <br>Metadata-driven, automated, friendly to predictive and NLP workloads.</p></li><li><p>Let people ask questions naturally. <br>Conversational analytics turns curiosity into insight.</p></li><li><p>Automate the Pipelines. <br>MLOps, DevOps, Kubernetes-anything that removes manual friction.</p></li></ul><p>And above all, build culture. Tech won&#8217;t magically turn an organization into an intelligent one. <em><strong>People do that.</strong></em></p><h3>The Tangible Payoff : </h3><p>Across industries, this evolution is already showing its teeth:</p><ul><li><p>In life sciences, AI is speeding up insight discovery around prescriber behavior.</p></li><li><p>In supply chains, predictive analytics trims delays-sometimes by a stunning margin.</p></li><li><p>In manufacturing, smarter quality control has reduced costly downtime.</p></li></ul><blockquote><p>In plain terms: faster decisions, fewer surprises, and measurable ROI.</p></blockquote><h3>What&#8217;s Coming Next</h3><p>The next phase is going to feel almost conversational-systems that pick up context, infer intent, and start anticipating what someone might need before they even ask. Dashboards won&#8217;t disappear, but they&#8217;ll feel more like companions than static panels.</p><h3>My Perspective from the Field</h3><p>As someone who&#8217;s guided a fair number of BI &#8594; AI transformations, I can say the toughest part is rarely the tech. It&#8217;s alignment-getting business, engineering, and data teams to move like a single organism instead of three neighbors sharing a wall. When that alignment clicks, the shift to AI isn&#8217;t just smooth; it&#8217;s something else entirely.</p><p>So here&#8217;s the question I end up asking leaders:</p><blockquote><p><em>Where is your organization on this curve? Still reporting the past, or already steering the future?</em></p></blockquote><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://ganeshkedari.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Geeky Tales&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://ganeshkedari.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Geeky Tales</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ganeshkedari.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Geeky Tales! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>