<?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[Databricksters]]></title><description><![CDATA[Field-tested Databricks solutions from specialists who deployed them]]></description><link>https://www.databricksters.com</link><image><url>https://substackcdn.com/image/fetch/$s_!zPJJ!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff49ecae-7c56-403c-9389-61b28de6a50f_1280x1280.png</url><title>Databricksters</title><link>https://www.databricksters.com</link></image><generator>Substack</generator><lastBuildDate>Wed, 04 Nov 2026 08:18:43 GMT</lastBuildDate><atom:link href="https://www.databricksters.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Soni]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[databricksters@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[databricksters@substack.com]]></itunes:email><itunes:name><![CDATA[Canadian Data Guy]]></itunes:name></itunes:owner><itunes:author><![CDATA[Canadian Data Guy]]></itunes:author><googleplay:owner><![CDATA[databricksters@substack.com]]></googleplay:owner><googleplay:email><![CDATA[databricksters@substack.com]]></googleplay:email><googleplay:author><![CDATA[Canadian Data Guy]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[That Wasn’t Very Cache Money of You]]></title><description><![CDATA[An easy to implement, realtime semantic cache on serverless Postgres with built-in LLMaaJ evaluation and drift monitoring]]></description><link>https://www.databricksters.com/p/that-wasnt-very-cache-money-of-you</link><guid isPermaLink="false">https://www.databricksters.com/p/that-wasnt-very-cache-money-of-you</guid><dc:creator><![CDATA[Austin]]></dc:creator><pubDate>Fri, 30 Oct 2026 18:59:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-P41!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-P41!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-P41!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png 424w, https://substackcdn.com/image/fetch/$s_!-P41!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png 848w, https://substackcdn.com/image/fetch/$s_!-P41!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png 1272w, https://substackcdn.com/image/fetch/$s_!-P41!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-P41!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png" width="638" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/be332d2b-a972-4816-8ebb-a7210402a56a_638x400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:638,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:289167,&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://www.databricksters.com/i/222831896?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.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_!-P41!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png 424w, https://substackcdn.com/image/fetch/$s_!-P41!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png 848w, https://substackcdn.com/image/fetch/$s_!-P41!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.png 1272w, https://substackcdn.com/image/fetch/$s_!-P41!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe332d2b-a972-4816-8ebb-a7210402a56a_638x400.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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 style="text-align: justify;">You ever look up at the stars and marvel at being here as the current version of you rather than the near infinite alternate versions that could have been? Yeah me neither. And for good reason; it turns out that we&#8217;re so predictable that any other versions would probably be doing similar stuff. On a related note, if we look at the aggregate of questions we pass to the standard AI assistant interface, the distribution of those questions appears astoundingly similar to a rank-frequency log normal distribution. That means if you send each one of those questions to an LLM, you are leaving money on the table.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.databricksters.com/subscribe?"><span>Subscribe now</span></a></p><p style="text-align: justify;">In 2024 Databricks published a <a href="https://www.databricks.com/blog/building-cost-optimized-chatbot-semantic-caching">semantic caching accelerator</a> that stored cached questions in a Vector Search index. This works great, but it was made before we had Lakebase, and a cache is naturally an OLTP workload: we want to write a row on every miss, delete rows on expiry, and filter by tenant and/or model before the ANN search. These features make pgvector on Lakebase a more compelling cache. Let&#8217;s see it in action.</p><p style="text-align: justify;">Imagine we have a customer-facing AI assistant in our client application. In the cacheless paradigm, the question goes into the application, maybe gets processed, and then gets handed to the LLM to return an answer to the customer. You pay for the FM API call every time. Now imagine we add a semantic cache. The question gets embedded and looked up in the pgvector cache, and if a high quality match is found, the cached answer is returned without invoking the LLM. If the threshold is not met, the question goes to the LLM and its answer is written back to the cache for future questions. Our cache is self-populating.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!A4YK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!A4YK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png 424w, https://substackcdn.com/image/fetch/$s_!A4YK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png 848w, https://substackcdn.com/image/fetch/$s_!A4YK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png 1272w, https://substackcdn.com/image/fetch/$s_!A4YK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!A4YK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png" width="1456" height="836" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f1f43993-1071-4429-930b-921761baa7d2_2025x1163.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:836,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&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_!A4YK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png 424w, https://substackcdn.com/image/fetch/$s_!A4YK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png 848w, https://substackcdn.com/image/fetch/$s_!A4YK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png 1272w, https://substackcdn.com/image/fetch/$s_!A4YK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1f43993-1071-4429-930b-921761baa7d2_2025x1163.png 1456w" sizes="100vw"></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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 style="text-align: justify;">The semantic match threshold serves as our main tuning parameter between high cost and latency savings on the one side and risking higher incorrect answers on the other. We have a few possible error paths here:</p><ul><li><p>The threshold is met, but the wrong answer is returned from the cache</p><ul><li><p>i.e. it should have picked a different cached answer</p></li></ul></li><li><p>The threshold is met, but the correct answer wasn&#8217;t in the cache</p><ul><li><p>i.e. it should have called the LLM instead</p></li></ul></li><li><p>The threshold is met, but the answer saved was factually inaccurate</p><ul><li><p>i.e. we need to better curate the cache</p></li></ul></li><li><p>The threshold is not met, and the LLM returned the incorrect answer</p><ul><li><p>i.e. we should have had more guardrails, better system context, etc.</p></li><li><p>It&#8217;s possible the correct answer was in the cache, but not triggered</p></li></ul></li></ul><p style="text-align: justify;">From my experience in building this, the threshold sweet spot is somewhere between 0.7 and 0.8. There is an inflection point in this range where the repeats caught drop dramatically, but so do the percent of wrong answers from the cache, regardless of the specific cache failure path. Luckily for us, we don&#8217;t need the repeat questions caught to reach a very high percentage of total asked for this to be economically advantageous, because the embedding model and Lakebase instance are both so cheap that intercepting even low single digit percentages of questions makes it worth it at high request volumes.</p><p style="text-align: justify;">A big question for enterprises considering a semantic cache is how to know what the tradeoff numbers look like before going to prod. That&#8217;s where a labeled evaluation set and an LLM judge come in. The judge takes a sample of hits the cache actually served, generates a fresh answer to each question, and decides whether the cached answer is correct. Before trusting it, check it against your labels. The verdicts land in a Delta table, and a Spark Declarative Pipeline turns them into a false-hit monitor compared against a limit you set. It reports OK, WATCH or ALERT and recommends an action, such as raising the threshold or not caching the categories causing wrong hits. You can run the judge from the app, or you could put it on a job and schedule it.</p><p style="text-align: justify;">Usability is also important for real world applications, so we also create a Genie Space over these tables that we can ask questions over in plain text, in realtime.</p><h2>FAQ</h2><ul><li><p>What if they ask user specific questions like account balance?</p><ul><li><p>Partition with scope keys and send questions involving live user data around the cache before lookup</p></li><li><p>Include probes like these in your eval set so you&#8217;ll see this behavior in testing</p></li></ul></li><li><p>Why pgvector? Why not Lakebase Search?</p><ul><li><p>Our project&#8217;s pgvector backend uses HNSW, Lakebase Search uses IVF</p></li><li><p>If the cache is large and loaded in bulk, then you can afford to reindex on a schedule, but IVF clusters cannot be incrementally updated</p></li></ul></li><li><p>What if the answer to a question changes, won&#8217;t I have to invalidate the cache to fix it?</p><ul><li><p>The system prompt&#8217;s hash is part of every cache key, so editing the fact sheet means old answers are never served again, with nothing to invalidate</p></li><li><p>You can also set a TTL for cached items</p></li></ul></li><li><p>How fast are hits?</p><ul><li><p>About 10x faster than misses; factor in about 150-200ms for the embedding call and maybe 10ms for the Lakebase lookup</p></li></ul></li><li><p>What does cache maintenance look like?</p><ul><li><p>This is one of the most important tunable features here, because more cache entries means more neighbors, and probably higher rates of the wrong answer being returned</p></li><li><p>You could use an async, batch process to leverage an LLM judge to periodically cull closely related semantic items and keep only the best, most popular ones</p></li></ul></li><li><p>What other tuning params are there?</p><ul><li><p>Threshold is the most important by far</p></li><li><p>hnsw.ef_search is probably a good one to explore when sweeping your eval set during configuration</p></li><li><p>The embedding model itself is also a swappable component</p></li></ul></li><li><p>Does this come with any guarantees or SLAs?</p><ul><li><p>No, this is purely field originated project, so for now it comes with no performance, functionality, or version guarantees</p></li><li><p>If you make a PR or raise an issue in the repo I will get to it on a best effort basis</p></li></ul></li></ul><p><a href="https://github.com/azaccor/lakebase-semantic-cache">Repo here.</a> Cheers and happy coding,</p><p>-Austin</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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[Multi-LoRA Serving on Databricks]]></title><description><![CDATA[Serve Multiple Fine-Tuned Models from a Single Databricks Endpoint with Multi-LoRA]]></description><link>https://www.databricksters.com/p/multi-lora-serving-on-databricks</link><guid isPermaLink="false">https://www.databricksters.com/p/multi-lora-serving-on-databricks</guid><dc:creator><![CDATA[Shailesh Saroha]]></dc:creator><pubDate>Thu, 29 Oct 2026 21:45:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!mOUG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mOUG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mOUG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!mOUG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!mOUG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!mOUG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mOUG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg&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;:2619980,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.databricksters.com/i/222586320?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mOUG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!mOUG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!mOUG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!mOUG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01528969-9076-4b58-8231-9170fcccffa9_2752x1536.jpeg 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 style="text-align: justify;"><em>LoRA adapters make fine-tuning lighter and faster, and multi-LoRA serving makes them inexpensive to run. This post explains when multi-LoRA is the right choice, and walks through serving a base model and several adapters from a single Custom LLM Serving endpoint with vLLM.</em></p><p><strong>Summary</strong></p><ul><li><p>If you have several fine-tuned variants of the same base model, you can usually serve them all from one GPU endpoint. Multi-LoRA serving keeps one copy of the base model in GPU memory and applies the requested adapter to each request, so you pay for one endpoint instead of one per model.</p></li><li><p>On Databricks, you package the base model, the adapters and the vLLM/SGLang command as one MLflow model, register it to Unity Catalog and deploy it with Custom LLM Serving. Each request then picks an adapter, or the base model, by name.</p></li><li><p>Multi-LoRA fits best when you have many adapters with moderate traffic each. For a single fine-tuned model with high traffic, where latency matters most, serving the merged weights is usually the better option.</p></li></ul><h2><strong>What LoRA is and why it helps</strong></h2><p>Fine-tuning every weight of a large language model is slow and expensive, and it produces a full copy of the model for every task. <a href="https://arxiv.org/abs/2106.09685">LoRA (Low-Rank Adaptation)</a> takes a lighter approach. It freezes the base model and trains a small pair of low-rank matrices next to selected layers, such as the attention and MLP projections. Only those matrices, called the adapter, are saved.</p><p>An adapter typically holds about 0.1% to 2% of the base model&#8217;s parameters, depending on its rank and the layers it targets. On disk, that is often tens to hundreds of megabytes for a model whose weights take many gigabytes. This brings three practical advantages. Training needs less GPU memory and time, so a single GPU is often enough. Adapters are cheap to store and version, so you can keep one per task, per customer or per experiment. And in many tasks, a well-trained adapter reaches quality close to full fine-tuning, although you should confirm that on your own evaluation data.</p><h2><strong>Multi-LoRA serving: one base model, many adapters</strong></h2><p>Because each adapter only adds to a shared base model, a serving engine can load the base model once and keep several adapters next to it. Multi-LoRA serving does exactly that. Every request names the adapter it wants, the engine applies that adapter&#8217;s small matrices on the fly, and requests for different adapters can share the same batch on the GPU.</p><p>In the example used throughout this post, one endpoint serves Qwen3.5-4B, a small open model, with three adapters: <code>finance</code>, <code>customer_support</code> and <code>text_to_sql</code>. A request with <code>model="finance"</code> gets the finance adapter, and a request with the base model&#8217;s name gets the base model, all from the same GPU.</p><p>This design brings four main benefits:</p><ul><li><p><strong>Lower cost.</strong> Several fine-tuned models share one GPU instead of each needing its own endpoint, which matters most when each model has moderate or uneven traffic.</p></li><li><p><strong>Fewer GPUs to secure.</strong> GPUs are in high demand, and every extra endpoint needs its own. Serving many models from one GPU means you need capacity for one endpoint instead of one per model.</p></li><li><p><strong>No cold start per model.</strong> All adapters are loaded when the endpoint starts, so switching between them does not wait for a model to load.</p></li><li><p><strong>Simpler operations.</strong> You manage, monitor and secure one endpoint for many specializations.</p></li></ul><p>Good fits include domain adapters on one base model (for example legal, medical, SQL generation and others), one adapter per customer or business unit, and A/B tests that compare adapter versions, with each request naming the version it uses.</p><h3><strong>When not to use it</strong></h3><p>Multi-LoRA is not the best choice for every case:</p><ul><li><p><strong>One high-traffic model where latency matters most.</strong> Applying an adapter adds some computation per request, which grows with the adapter rank and the number of different adapters in a batch. Merged weights, with the adapter folded into the base model, avoid that cost and work with engine optimizations that may not support adapters, such as some speculative decoding methods.</p></li><li><p><strong>Adapters on different base models.</strong> All adapters on one endpoint must share the same base model.</p></li><li><p><strong>Adapters that need separate access.</strong> Access is controlled per endpoint, so anyone who can query the endpoint can call every adapter on it. Keep such adapters, for example some per-customer ones, on separate endpoints.</p></li><li><p><strong>Adapter sets that change many times a day.</strong> Adding new adapters to a running endpoint dynamically is not currently supported on Databricks, so every adapter must be included when you log the model. To add one later, you register a new model version and update the endpoint.</p></li><li><p><strong>Endpoints that split traffic.</strong> To select an adapter, the endpoint must serve a single model with 100% of its traffic. Endpoints that split traffic between models or versions can&#8217;t select an adapter.</p></li></ul><h2><strong>Custom LLM Serving on Databricks</strong></h2><p><a href="https://docs.databricks.com/aws/en/machine-learning/model-serving/serve-custom-llms">Custom LLM Serving</a> runs your own LLM on a <a href="https://docs.databricks.com/aws/en/machine-learning/model-serving/">Model Serving</a> GPU endpoint, with an OpenAI-compatible API. You choose the inference engine and its settings, test the exact command in an <a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/">AI Runtime</a> notebook, and deploy the same command and environment to production.</p><p>Custom LLM Serving is the right choice when you want to serve an open model, or a fine-tuned version of one, that the <a href="https://docs.databricks.com/aws/en/machine-learning/foundation-model-apis/">Databricks Foundation Model APIs</a> offering does not include, when you want to modify the model&#8217;s architecture, or when you need control over the engine and its configuration. If Foundation Model APIs already offers the model you need, it is the recommended option, because it is managed, optimized and simple to use.</p><p>Serving several LoRA adapters is a natural fit for Custom LLM Serving, because the engine&#8217;s own multi-LoRA support does the work. The documentation covers it in <a href="https://docs.databricks.com/aws/en/machine-learning/model-serving/serve-custom-llms#serve-lora-adapters">Serve LoRA adapters</a>. Custom LLM Serving is in Public Preview, so check the documentation for its current status.</p><h2><strong>Serving multiple LoRA adapters with vLLM</strong></h2><p>The workflow has five steps. You put the base model and adapters in one folder, write the vLLM command that serves them, test that command in the notebook, log and register the model to <a href="https://docs.databricks.com/aws/en/data-governance/unity-catalog/">Unity Catalog</a>, and deploy the endpoint. Run these steps in an AI Runtime notebook with a GPU, because the endpoint reuses the notebook&#8217;s Python environment. The AI environment comes with vLLM preinstalled, while a Standard environment lets you install the libraries and versions you choose.</p><h3><strong>Step 1: Prepare the base model and adapters</strong></h3><p>Everything the endpoint needs lives under a single folder, which is logged as one artifact. Each adapter is a standard <a href="https://huggingface.co/docs/peft/index">PEFT</a> folder with an <code>adapter_config.json</code> and an <code>adapter_model.safetensors</code> file, and the folder names become the names that requests use.</p><pre><code><code>weights/
  base/        # base model weights and tokenizer
  finance/     # adapter_config.json, adapter_model.safetensors
  customer_support/
  text_to_sql/</code></code></pre><p>The adapters can come from your own fine-tuning runs on AI Runtime, saved to a Unity Catalog volume, or from Hugging Face. For example, to copy an adapter from a volume:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;3a569f4c-9aae-4366-b21f-b40c8fa41dd8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import shutil
shutil.copytree("/Volumes/&lt;catalog&gt;/&lt;schema&gt;/&lt;volume&gt;/finance/adapter", "weights/finance")</code></pre></div><p>Before you deploy, check that every adapter&#8217;s <code>adapter_config.json</code> names the same base model that you serve. If you use an open base model or public adapters, also check that their licenses allow commercial use by your organization.</p><p><strong>Note for Qwen3.5 and Qwen3.8:</strong> vLLM and SGLang load these models as vision-language models, so adapters must use <code>model.language_model.layers.*</code> key names. Adapters saved with <code>model.layers.*</code> load without error but behave like the base model, so rename those keys before you log the model.</p><h3><strong>Step 2: Build the vLLM entrypoint</strong></h3><p>The entrypoint is the command the endpoint runs to start vLLM. It must listen on port 8080, and its paths are relative to the logged artifacts, so the same command works in the notebook and on the endpoint.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;b7187233-440c-4806-9edd-fa91903cc5a5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">ADAPTERS = ["finance", "customer_support", "text_to_sql"]
ENDPOINT_NAME = "multi-lora-vllm"

def vllm_command(port):
    return " ".join([
        "VLLM_USE_FLASHINFER_SAMPLER=0",
        "python -m vllm.entrypoints.openai.api_server",
        "--model weights/base",
        f"--served-model-name base {ENDPOINT_NAME}",  # names that select the base model
        "--enable-lora",
        "--lora-modules " + " ".join(f"{a}=weights/{a}" for a in ADAPTERS),
        f"--max-loras {len(ADAPTERS)}",  # adapters on the GPU at once
        "--max-lora-rank 16",            # at least the largest adapter rank
        "--max-model-len 2048",
        """--default-chat-template-kwargs '{"enable_thinking": false}'""",  # Qwen3.5 thinks by default
        f"--host 0.0.0.0 --port {port}",
    ])</code></pre></div><p>The LoRA flags carry the multi-LoRA setup. </p><ul><li><p><code>--enable-lora</code> turns on adapter support</p></li><li><p> <code>--lora-modules</code> registers each adapter under its name</p></li><li><p><code>--max-loras</code> sets how many adapters can be active in one batch</p></li><li><p><code>--max-lora-rank</code> must be at least the largest rank among your adapters, rounded up to a value vLLM accepts. Set it from your adapters&#8217; configs, because higher values reserve more GPU memory. </p></li></ul><p>If you serve more adapters than <code>--max-loras</code>, raise <code>--max-cpu-loras</code> to cover all of them. vLLM then keeps the extra adapters in CPU memory and moves them onto the GPU as requests arrive, so test with your expected mix of traffic to choose <code>--max-loras</code>. </p><p>Qwen3.5 thinks before it answers by default, so <code>--default-chat-template-kwargs</code> turns thinking off and the adapters answer directly. A request can still turn it on.</p><h3><strong>Step 3: Test in the notebook</strong></h3><p>Before you log anything, start the same command in the notebook and send a request to each adapter. This catches most configuration problems in minutes instead of after a deployment. Notebooks can listen on ports 3000 to 3999, so the test uses port 3080.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;1133c7d7-fc96-4e83-b8a6-085a7a09dbf2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import os, signal, subprocess, requests

server = subprocess.Popen(vllm_command(3080), shell=True, start_new_session=True)

# Wait until http://localhost:3080/health responds, then:
print(requests.get("http://localhost:3080/v1/models").json())  # base, the endpoint name and every adapter

for name in ["base", *ADAPTERS]:
    reply = requests.post("http://localhost:3080/v1/chat/completions", json={
        "model": name, "messages": [{"role": "user", "content": "What is EBITDA?"}], "max_tokens": 64,
    })
    print(name, reply.json()["choices"][0]["message"]["content"])

os.killpg(server.pid, signal.SIGTERM)  # stop vLLM and free the GPU</code></pre></div><p>Compare each answer with what the adapter was trained to do. Stopping the whole process group matters, because <code>server.terminate()</code> would stop only the shell and leave vLLM running on the GPU.</p><h3><strong>Step 4: Log and register the model to Unity Catalog</strong></h3><p>The endpoint runs the entrypoint directly and never calls the model&#8217;s <code>predict</code> method. MLflow still needs a model class to log, so a small placeholder class is enough. The <code>metadata</code> carries the two settings the endpoint reads: <code>task</code>, which sets the API the endpoint serves, and <code>entrypoint</code>, the vLLM command. The entrypoint is logged with port 8080, because Model Serving sends requests to the server on that port, while the notebook test used port 3080.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;93e16dbd-425a-45e7-95b6-2136accef748&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import mlflow
from mlflow.pyfunc.model import ChatCompletionResponse, ChatModel

class Placeholder(ChatModel):  # never called; the endpoint runs the entrypoint
    def predict(self, context, messages, params):
        return ChatCompletionResponse.from_dict({"choices": []})

mlflow.set_registry_uri("databricks-uc")
with mlflow.start_run():
    model_info = mlflow.pyfunc.log_model(
        name="multi_lora_vllm",
        python_model=Placeholder(),
        artifacts={"weights": "weights"},
        metadata={"task": "llm/v1/chat", "entrypoint": vllm_command(8080)},
    )

version = mlflow.register_model(
    model_info.model_uri, "main.models.multi_lora_vllm", env_pack="databricks_model_serving"
).version</code></pre></div><p>Setting <code>env_pack="databricks_model_serving"</code> packages the notebook&#8217;s Python environment with the model, so the endpoint runs the same vLLM version that you tested and does not need to rebuild it.</p><h3><strong>Step 5: Deploy the endpoint</strong></h3><p>Create the endpoint with the <a href="https://docs.databricks.com/aws/en/dev-tools/sdk-python">Databricks SDK</a>, choosing a GPU workload type that fits the base model, plus the adapters and the KV cache.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;28fbece3-7e9a-4de2-bec9-5a375db641f3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import mlflow
from mlflow.pyfunc.model import ChatCompletionResponse, ChatModel

class Placeholder(ChatModel):  # never called; the endpoint runs the entrypoint
    def predict(self, context, messages, params):
        return ChatCompletionResponse.from_dict({"choices": []})

mlflow.set_registry_uri("databricks-uc")
with mlflow.start_run():
    model_info = mlflow.pyfunc.log_model(
        name="multi_lora_vllm",
        python_model=Placeholder(),
        artifacts={"weights": "weights"},
        metadata={"task": "llm/v1/chat", "entrypoint": vllm_command(8080)},
    )

version = mlflow.register_model(
    model_info.model_uri, "main.models.multi_lora_vllm", env_pack="databricks_model_serving"
).version</code></pre></div><p>One A10G fits the 4B model in this example. For a larger base model, such as Qwen3.8-27B, use a 1x H100 workload type (<code>GPU_XLARGE</code>) or RTX Pro 6000 Blackwell, and serve the adapters the same way. Size the endpoint for your peak traffic, and for production, consider turning off scale to zero to avoid the startup wait after idle periods. The <a href="https://docs.databricks.com/aws/en/machine-learning/model-serving/serve-custom-llms#choose-a-gpu">documentation</a> lists the available GPU types and the model sizes each one suits.</p><h2><strong>Calling an adapter or the base model</strong></h2><p><strong>The recommended way to call the endpoint is the OpenAI client on the chat route</strong>, with the endpoint named in the <code>Databricks-Model-Serving-Endpoint</code> header. The request body reaches vLLM unchanged, so the <code>model</code> field selects the adapter, and the base model&#8217;s served name selects the base model. The documentation shows the same calls in <a href="https://docs.databricks.com/aws/en/machine-learning/model-serving/serve-custom-llms#query-a-lora-adapter">Query a LoRA adapter</a>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;3919ba9e-1e21-4955-93bb-c19e6a6a2472&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from openai import OpenAI

client = OpenAI(
    api_key=DATABRICKS_TOKEN,
    base_url=f"{DATABRICKS_HOST}/serving-endpoints",
    default_headers={"Databricks-Model-Serving-Endpoint": ENDPOINT_NAME},
)
messages = [{"role": "user", "content": "What is EBITDA?"}]

finance = client.chat.completions.create(model="finance", messages=messages)  # finance adapter
base = client.chat.completions.create(model="base", messages=messages)        # base model</code></pre></div><p>If you are not using Python, send the same request over REST to the same route. The header names the endpoint, and the <code>model</code> field in the body names the adapter.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;shell&quot;,&quot;nodeId&quot;:&quot;89ecd5de-a941-43b1-aee8-89304a189093&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-shell">curl "$DATABRICKS_HOST/serving-endpoints/chat/completions" \
  -H "Authorization: Bearer $DATABRICKS_TOKEN" \
  -H "Databricks-Model-Serving-Endpoint: multi-lora-vllm" \
  -H "Content-Type: application/json" \
  -d '{"model": "text_to_sql", "messages": [{"role": "user", "content": "Count orders by status."}]}'</code></pre></div><p>A name that was not loaded returns an error, so a typo does not silently fall back to the base model. </p><p><strong>Use the chat route whenever a request needs an adapter.</strong> Other routes, such as <code>/serving-endpoints/&lt;endpoint&gt;/invocations</code> (used by the Databricks SDK&#8217;s <code>query()</code> method), serve the base model with vLLM.</p><h2><strong>Serving adapters with SGLang</strong></h2><p><a href="https://docs.sglang.io/">SGLang</a> also supports multi-LoRA serving, and the Databricks workflow is the same once you install SGLang in the notebook environment. Only the command changes: use <code>--enable-lora</code> and <code>--lora-paths</code> to register the adapters, and set <code>--max-loras-per-batch</code> to the number of adapters plus one, because base-model requests take a slot too.</p><p>To call an adapter, use the OpenAI client with the same <code>base_url</code> but without the endpoint header, set <code>model</code> to the endpoint name, and pass the adapter name with <code>extra_body={"lora_path": "finance"}</code>. Without <code>lora_path</code>, the request goes to the base model. Note that <code>model="finance"</code>, which selects the adapter in vLLM, serves the base model in SGLang.</p><h2><strong>Get started</strong></h2><p>Multi-LoRA serving lets one GPU endpoint do the work of many, which lowers cost and keeps operations simple when you have several fine-tuned variants of one base model. To try it, fine-tune few adapters on <a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/">AI Runtime</a>, deploy them together with <a href="https://docs.databricks.com/aws/en/machine-learning/model-serving/serve-custom-llms">Custom LLM Serving</a>, and compare the cost of that one endpoint with the cost of one endpoint per model. </p><p>For a ready-made start, the documentation includes a <a href="https://docs.databricks.com/aws/en/notebooks/source/machine-learning/custom-llm-serving/qwen3-5-4b-lora.html">Qwen3.5-4B and LoRA adapters starter notebook</a>. Evaluate each adapter on your own data, and benchmark your own workload on each candidate GPU before you choose a configuration for production.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><item><title><![CDATA[How to Connect Your Enterprise Apps to Claude, Cursor, Gemini and Genie One]]></title><description><![CDATA[A step-by-step guide to OAuth, Unity Catalog MCP Services, and enterprise identity for AI agents, with no proxy scripts or shared tokens.]]></description><link>https://www.databricksters.com/p/how-to-connect-your-enterprise-apps</link><guid isPermaLink="false">https://www.databricksters.com/p/how-to-connect-your-enterprise-apps</guid><dc:creator><![CDATA[Canadian Data Guy]]></dc:creator><pubDate>Thu, 22 Oct 2026 11:09:32 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d654bb5e-1593-4c3f-a084-98d6f6020f6e_1008x1061.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You built a Databricks App that serves an MCP server. Nice. Now people want its tools in Genie One, Cursor, and Claude Code. Skip the shared service principal token; all three can call your app as the person using them.</p><h2>TL;DR</h2><ul><li><p>Cursor and Claude Code call <code>https://&lt;your-app-url&gt;/mcp/</code> directly; Genie One goes through a Unity Catalog MCP Service and HTTP connection.</p></li><li><p>Either way, the app gets a token its workspace issued to the human user, and the Databricks Apps proxy forwards that user&#8217;s identity.</p></li><li><p>The only account-admin step: register one public OAuth client with each AI client&#8217;s redirect URI. The rest is client config and grants.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7Z6t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7Z6t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif 424w, https://substackcdn.com/image/fetch/$s_!7Z6t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif 848w, https://substackcdn.com/image/fetch/$s_!7Z6t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif 1272w, https://substackcdn.com/image/fetch/$s_!7Z6t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7Z6t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif" width="1200" height="1341" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1341,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1719233,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.databricksters.com/i/220787768?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7Z6t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif 424w, https://substackcdn.com/image/fetch/$s_!7Z6t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif 848w, https://substackcdn.com/image/fetch/$s_!7Z6t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif 1272w, https://substackcdn.com/image/fetch/$s_!7Z6t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8309e27-7ca0-40d9-998b-61348e59c952_1200x1341.gif 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2>How it works: two paths, one identity</h2><p><strong>Direct path.</strong> Cursor and Claude Code connect to the app&#8217;s Streamable HTTP MCP endpoint, <code>https://&lt;your-app-url&gt;/mcp/</code>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><strong>Governed path.</strong> Genie One calls a Unity Catalog MCP Service, which calls the same app URL through a Unity Catalog HTTP connection.</p><p><strong>Identity.</strong> On both paths, the app receives a token issued by the app&#8217;s workspace for the human user. The Databricks Apps proxy validates it and forwards the user&#8217;s identity (<code>X-Forwarded-Email</code>, <code>X-Forwarded-User</code>). The app resolves the user from those headers, so data is owned by the person.</p><p><strong>Discovery.</strong> Databricks Apps publish OAuth protected-resource metadata (<a href="https://datatracker.ietf.org/doc/html/rfc9728">RFC 9728</a>) at <code>/.well-known/oauth-protected-resource/mcp/</code>, pointing at the workspace&#8217;s OIDC server. Databricks OIDC supports PKCE and public clients (no secret; PKCE protects the sign-in instead), but not dynamic client registration. So AI clients can&#8217;t register themselves; they need a pre-registered public OAuth client, and one can serve all three.</p><p>That last point explains most of the setup, and most of the errors.</p><h2>The one account-admin step: register a public OAuth client</h2><p>An account admin of the account that owns the app&#8217;s workspace creates a public (no secret) custom OAuth app connection: <strong>Account Console &#8594; Settings &#8594; App connections</strong>, or <code>databricks account custom-app-integration create</code>.</p><ul><li><p><strong>Scopes:</strong> <code>all-apis offline_access</code> (optionally <code>openid email profile</code>).</p></li><li><p><strong>Redirect URIs:</strong></p><ul><li><p>Cursor: <code>http://localhost:8787/callback</code>, the callback current versions use</p></li><li><p>Claude Code: <code>http://localhost:&lt;callbackPort&gt;/callback</code>, for example port 8080</p></li><li><p>Genie One: <code>https://&lt;genie-workspace-host&gt;/login/oauth/http.html</code>, for every hostname that workspace uses, vanity ones included</p></li></ul></li></ul><p>Updates replace the whole redirect list, so always include the existing URIs. Then grant <code>CAN_USE</code> on the app to the users and groups who should use it.</p><h2>Connect Cursor to the MCP endpoint</h2><p>Add the server to <code>~/.cursor/mcp.json</code>. Current Cursor versions read <code>auth.CLIENT_ID</code> and ignore the Claude-style <code>oauth.clientId</code>.</p><pre><code><code>{
  "mcpServers": {
    "my-app": {
      "url": "https://&lt;your-app-url&gt;/mcp/",
      "auth": { "CLIENT_ID": "&lt;client-id&gt;", "scopes": ["all-apis", "offline_access"] }
    }
  }
}</code></code></pre><p>Then open <strong>Settings &#8594; Tools &amp; MCP</strong>, click <strong>Connect</strong>, and sign in once in the browser.</p><h2>Connect Claude Code to the MCP endpoint</h2><p>Add the server with the CLI, then add scopes so its <code>mcpServers</code> entry in <code>~/.claude.json</code> matches the JSON below.</p><pre><code><code>claude mcp add --transport http --client-id &lt;client-id&gt; --callback-port 8080 -s user my-app https://&lt;your-app-url&gt;/mcp/</code></code></pre><pre><code><code>"my-app": {
  "type": "http",
  "url": "https://&lt;your-app-url&gt;/mcp/",
  "oauth": { "clientId": "&lt;client-id&gt;", "callbackPort": 8080, "scopes": "all-apis offline_access" }
}</code></code></pre><p>Run <code>/mcp</code>, select the server, and choose <strong>Authenticate</strong>. Both clients store and refresh tokens themselves. No proxy scripts, no token commands.</p><h2>Connect Genie One through Unity Catalog</h2><p>In the Genie One workspace, create a Unity Catalog HTTP connection:</p><ul><li><p><strong>Type:</strong> OAuth U2M (user-to-machine), per user</p></li><li><p><strong>Host:</strong> <code>https://&lt;your-app-url&gt;</code>, with base path <code>/mcp/</code></p></li><li><p><strong>Authorization endpoint:</strong> <code>https://&lt;app-workspace-host&gt;/oidc/v1/authorize</code></p></li><li><p><strong>Token endpoint:</strong> <code>https://&lt;app-workspace-host&gt;/oidc/v1/token</code></p></li><li><p><strong>Client ID and scope:</strong> your public client ID and <code>all-apis offline_access</code></p></li></ul><p>Then create a Unity Catalog MCP Service on top of it, such as <code>&lt;catalog&gt;.&lt;schema&gt;.&lt;service&gt;</code>. Each user signs in once (from Genie One&#8217;s connections or the service&#8217;s sign-in page), attaches the service in a chat (<strong>+ &#8594; More connections</strong>), and asks Genie to use the tools.</p><h2>How Unity Catalog governs Genie One access</h2><ul><li><p><strong>The MCP Service is the access check.</strong> It decides who may call: USE CATALOG and USE SCHEMA on its catalog and schema, plus EXECUTE on the service.</p></li><li><p><strong>The HTTP connection is token management.</strong> It stores each user&#8217;s token from their one-time sign-in, refreshes it, and attaches it to calls.</p></li><li><p><strong>Callers need no privileges on the connection itself.</strong></p></li><li><p><strong>Tokens are per user.</strong> Every Genie One call carries the caller&#8217;s own token, never a shared one.</p></li><li><p><strong>Use a catalog and schema your audience can reach.</strong> A personal catalog only works for you.</p></li></ul><h2>Genie One in a different workspace? Same setup</h2><p>Genie One can live in another workspace, even on another cloud: the connection signs users in against the app&#8217;s workspace, which issues the token. Register the Genie workspace&#8217;s callback on the public client, and if the app workspace uses IP access lists, allow the Genie workspace&#8217;s gateway egress.</p><h2>Five traps we hit, and their fixes</h2><p>The first two stem from false assumptions: that you can borrow the app&#8217;s own OAuth client, and that clients register themselves.</p><ul><li><p><code>invalid_client</code><strong> at token exchange.</strong> You reused the app&#8217;s built-in OAuth client. It&#8217;s confidential; only the Apps platform has its secret. Use a separate public client.</p></li><li><p><code>does not support dynamic client registration</code><strong>.</strong> The client isn&#8217;t reading your client ID (wrong config key), or a running session still has old config. Fix the key, then reconnect.</p></li><li><p><code>redirect_uri ... not registered</code><strong>.</strong> The OAuth client is missing that AI client&#8217;s callback, or the Genie workspace&#8217;s vanity hostname. Add it.</p></li><li><p><strong>Default scopes.</strong> Unless you pin them, clients request the scopes the app advertises, often <code>iam.*</code>, which may not be on your client. Pin <code>all-apis offline_access</code>.</p></li><li><p><code>Not Acceptable: Client must accept text/event-stream</code><strong>.</strong> You opened <code>/mcp/</code> in a browser. That&#8217;s normal.</p></li></ul><h2>Why it matters</h2><p>One app, one sign-in model, every AI client: real-user identity on every call, Unity Catalog governance for Genie One, and no shared token or proxy script to babysit. For more, see the Databricks docs on <a href="https://docs.databricks.com/aws/en/generative-ai/mcp/">MCP</a>, <a href="https://docs.databricks.com/aws/en/generative-ai/mcp/custom-mcp">custom MCP servers</a>, and <a href="https://docs.databricks.com/aws/en/dev-tools/databricks-apps/auth">Apps authorization</a>, which covers both identity models.</p><h2>FAQ</h2><p><strong>Q: What is an MCP server on Databricks Apps?</strong> A: An MCP server you build yourself and run on . Apps provides the compute and an HTTPS URL that AI clients connect to over <a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports">Streamable HTTP</a>. You can start from a template, wrap an existing REST API, or deploy an MCP server you already have. See and the <a href="https://learn.microsoft.com/en-us/azure/databricks/agents/mcp-tools/index/">MCPs overview</a>.</p><p><strong>Q: How is this different from Databricks-provided or external MCP servers?</strong> A: <a href="https://learn.microsoft.com/en-us/azure/databricks/agents/mcp-tools/built-in-mcp-services/">Databricks-provided MCPs</a> (Genie One, Databricks SQL, Slack, GitHub and others) live in <code>system.ai</code> and need no hosting. <a href="https://learn.microsoft.com/en-us/azure/databricks/ai-gateway/register-mcp-service/">External MCP servers</a> are third-party or self-hosted servers you register in Unity Catalog. A custom server on Databricks Apps is your own code. This post registers it as an external MCP Service so Genie One can use it.</p><p><strong>Q: Do I need a separate MCP server for each AI client?</strong> A: No. Cursor, Claude Code and Genie One all use the same <code>https://&lt;your-app-url&gt;/mcp/</code> endpoint and the same public OAuth client.</p><p><strong>Q: What&#8217;s the difference between app authorization and user authorization?</strong> A: With app authorization, everyone shares the app&#8217;s service principal. With user authorization (on-behalf-of-user), Databricks forwards the user&#8217;s identity and token, so Unity Catalog row filters and column masks apply to that person. See and <a href="https://learn.microsoft.com/en-us/azure/databricks/agents/custom-agents/agent-authentication/">Authentication for agents</a>.</p><p><strong>Q: Can I use a personal access token instead of OAuth?</strong> A: Not for servers on Databricks Apps; <a href="https://learn.microsoft.com/en-us/azure/databricks/ai-gateway/coding-agent-claude/">those require OAuth</a>. PATs only work for local testing against Databricks-provided or registered MCPs.</p><p><strong>Q: Why can&#8217;t Cursor or Claude Code register their own OAuth client?</strong> A: Databricks MCP endpoints (<a href="https://datatracker.ietf.org/doc/html/rfc7591">RFC 7591</a>). So an account admin pre-registers one public client that uses <a href="https://datatracker.ietf.org/doc/html/rfc7636">PKCE</a>. Discovery follows the <a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization">MCP authorization spec</a> and <a href="https://datatracker.ietf.org/doc/html/rfc9728">RFC 9728 protected-resource metadata</a>.</p><p><strong>Q: How do I create the public OAuth client?</strong> A: In the account console, go to <strong>Settings &#8594; App connections &#8594; Add connection</strong> and clear <strong>Generate a client secret</strong>. Or run <code>databricks account custom-app-integration create</code>. See . <code>all-apis</code> covers non-SQL APIs, and <code>offline_access</code> lets clients get refresh tokens.</p><p><strong>Q: What scopes should I request?</strong> A: For an app-hosted server, include the app&#8217;s user-authorization scopes plus <code>offline_access</code>, and grant <strong>CAN USE</strong> on the app. See .</p><p><strong>Q: How do I connect Cursor and Claude Code?</strong> A: Start with the Databricks guides for <a href="https://learn.microsoft.com/en-us/azure/databricks/ai-gateway/coding-agent-cursor/">Cursor</a> and <a href="https://learn.microsoft.com/en-us/azure/databricks/ai-gateway/coding-agent-claude/">Claude Code</a>. Each client&#8217;s own MCP docs cover its config details: <a href="https://docs.cursor.com/context/mcp">Cursor MCP docs</a> and <a href="https://docs.anthropic.com/en/docs/claude-code/mcp">Claude Code MCP docs</a>. Using Codex instead? See <a href="https://learn.microsoft.com/en-us/azure/databricks/ai-gateway/coding-agent-codex/">Connect Codex</a>.</p><p><strong>Q: What is a Unity Catalog HTTP connection, and do Genie One users need privileges on it?</strong> A: An is a Unity Catalog securable that stores endpoint and credential details and handles OAuth token refresh and per-user sign-in. When you go through an MCP Service, users need <strong>USE CATALOG</strong>, <strong>USE SCHEMA</strong> and <strong>EXECUTE</strong> on the service, not privileges on the connection. See <a href="https://learn.microsoft.com/en-us/azure/databricks/connect/uc-connections/">Unity Catalog connections</a>.</p><p><strong>Q: Can I also use Genie One from Claude or ChatGPT?</strong> A: Yes. The <a href="https://learn.microsoft.com/en-us/azure/databricks/agents/mcp-tools/genie-mcp/">Genie One MCP server</a> works in the other direction: it exposes Genie to outside MCP clients.</p><p><strong>Q: Does this work when Genie One is in a different workspace or cloud?</strong> A: Yes, as long as the connection signs users in against the app&#8217;s workspace. If that workspace uses <a href="https://learn.microsoft.com/en-us/azure/databricks/security/network/front-end/ip-access-list-workspace/">IP access lists</a>, allow the Genie workspace&#8217;s egress IPs.</p><p><strong>Q: I changed my OAuth client and nothing happened. Why?</strong> A: Changes to an OAuth app connection . Wait, then reconnect the client.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><item><title><![CDATA[Designing Domains and Subdomains in Unity Catalog]]></title><description><![CDATA[Architecture Principles for a Governed Data Estate]]></description><link>https://www.databricksters.com/p/designing-domains-and-subdomains</link><guid isPermaLink="false">https://www.databricksters.com/p/designing-domains-and-subdomains</guid><dc:creator><![CDATA[Allan Cao]]></dc:creator><pubDate>Tue, 13 Oct 2026 14:03:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_wkU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>TL;DR</h2><ul><li><p><strong>Domains</strong> are a Unity Catalog organizing and discovery layer, built on governed tags &#8212; not a new securable type, and not an access-control mechanism.</p></li><li><p>They sit on top of your existing catalog/schema layout, letting you overlay business structure (Finance, Customer, Risk) without moving data.</p></li><li><p>Design top-level domains around business capabilities &#8212; Function, Region, Brand, or Business Unit &#8212; and spend your one subdomain level deliberately.</p></li><li><p>Keep domains, meaning (Pages), measures (Metric Views), and access (grants/ABAC) as separate concerns; collapsing them is the most common design mistake.</p></li><li><p>Access always stays with Unity Catalog privileges &#8212; a domain&#8217;s Owner and Contact fields are metadata only.</p></li></ul><p></p><h2>Where domains fit in Genie Ontology</h2><p><strong>Genie Ontology</strong> is the AI-ready business semantic layer that grounds <a href="https://docs.databricks.com/aws/en/genie">Genie</a> and every business-facing search &#8212; it&#8217;s how the platform knows what your data <em>means</em>, not just where it sits. It combines a <strong>Modelled Ontology</strong> you curate and govern with <strong>inferred context</strong> Genie learns automatically from existing tables, queries, dashboards, and notebooks. Each piece of context gets an authority score based on source, usage, and freshness; Genie ranks the relevant pieces, resolves conflicts, and answers using only sources each user is permitted to see.</p><p>The Modelled Ontology has three components, grouped as <strong><a href="https://docs.databricks.com/aws/en/uc-semantics">Unity Catalog Semantics</a></strong>: Domains, Pages, and Metric Views. The most common design mistake is collapsing them &#8212; treating a domain as if it also carried meaning, metric logic, or access rights. It doesn&#8217;t. <strong>Domains organize</strong> assets by business purpose and assign an owner; <strong>Pages supply meaning</strong>; <strong>Metric Views supply measures</strong>. Each Page belongs to exactly one domain or subdomain, and a domain typically holds many terms and metrics, so domains are the scaffold the other two hang from.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_wkU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_wkU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png 424w, https://substackcdn.com/image/fetch/$s_!_wkU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png 848w, https://substackcdn.com/image/fetch/$s_!_wkU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!_wkU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_wkU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png" width="1456" height="1458" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1458,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!_wkU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png 424w, https://substackcdn.com/image/fetch/$s_!_wkU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png 848w, https://substackcdn.com/image/fetch/$s_!_wkU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!_wkU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0530c929-26af-4ef8-b84d-c00040cf5ac4_1598x1600.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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><em>Figure 1. Where domains sit: above the physical layout, granting no access.</em></p><p>That scoping matters as the semantic layer grows. Once an estate holds hundreds of Metric Views across dozens of business areas, the hard part stops being <em>reaching</em> a definition and becomes <em>choosing</em> the right one &#8212; and domain, owner, and certification are exactly the filters that narrow that choice. The same curation pays off twice: it makes the estate easier for people to browse and gives Genie a trusted, business-scoped signal for retrieval.</p><p></p><h2>Why domains, and why now</h2><p>Most Unity Catalog estates are organized the way they were built &#8212; by source system, pipeline, or environment &#8212; which rarely matches how the business thinks about its data (&#8221;Marketing,&#8221; &#8220;Risk,&#8221; &#8220;Customer 360&#8221;). A <strong>domain</strong> is the top-level unit of data ownership: a business function, region, brand, or unit with a real, accountable team. Subdomains add one level of nesting (e.g., Finance &#8594; Tax).</p><p>This separates <strong>physical data ownership</strong> (who runs the pipeline) from <strong>business ownership</strong> (who defines what the data means) &#8212; one of the main reasons domains exist. In a small team, the people who build a pipeline also know what the data means and who should use it. As organizations grow, these jobs come apart: pipeline teams own <em>how</em> data arrives, while business stewards own <em>what</em> it means. Domains give the second group a place to stand, independent of where the bytes live.</p><p>It helps to name the two areas of responsibility explicitly, so problems land with the right owner. A pipeline owner is accountable for freshness and SLAs, job failures, schema changes, and cost &#8212; &#8220;the table didn&#8217;t refresh this morning&#8221; gets fixed in the pipeline or job. A domain owner or data steward is accountable for business logic, metric definitions, certification, and being the person to ask &#8212; &#8220;revenue here excludes refunds, and it shouldn&#8217;t&#8221; gets fixed in the domain&#8217;s Pages, Metric Views, and asset metadata.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jSwZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jSwZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png 424w, https://substackcdn.com/image/fetch/$s_!jSwZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png 848w, https://substackcdn.com/image/fetch/$s_!jSwZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png 1272w, https://substackcdn.com/image/fetch/$s_!jSwZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!jSwZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png" width="1456" height="1061" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1061,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!jSwZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png 424w, https://substackcdn.com/image/fetch/$s_!jSwZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png 848w, https://substackcdn.com/image/fetch/$s_!jSwZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png 1272w, https://substackcdn.com/image/fetch/$s_!jSwZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7bf9a15-0cb9-46bc-bdc8-b732a46bd53d_1600x1166.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><em>Figure 2. Two kinds of ownership, and where each kind of problem gets fixed.</em></p><p>When everything is owned by the pipeline team by default, business-logic questions end up in engineering backlogs and nobody signs off on what a number means. Domains make the business side of ownership visible and assignable.</p><p>The idea isn&#8217;t new &#8212; it&#8217;s common across enterprise catalogs and Data Mesh/Data Fabric architectures. What&#8217;s different is that Databricks makes domains <strong>native to the platform</strong>, built on Unity Catalog governed tags, rather than bolted on via a separate product.</p><p><em>As of this writing, Domains, subdomains, and Discover are in Public Preview on AWS, Azure, and GCP; Pages is in Beta; Genie Ontology is in Public Preview; Metric Views are GA. Check the docs for current status.</em></p><p></p><h2>How domains actually work</h2><p>A few mechanics drive almost every design decision:</p><ul><li><p>A domain is a <strong>governed tag designated as a domain</strong> &#8212; not a new securable. A subdomain is a child tag ({parentDomain}/{subdomain}), nested <strong>exactly one level deep</strong>.</p></li><li><p>Assets join a domain by applying the backing tag, via the &#8220;Add to Domain&#8221; picker, or indirectly through a subdomain. Membership is <strong>many-to-many</strong> &#8212; any taggable securable or workspace asset can belong to multiple domains at once.</p></li><li><p><strong>Each domain is its own tag key, not a value</strong> &#8212; that&#8217;s what makes multi-domain membership possible, since tag values are mutually exclusive but keys aren&#8217;t.</p></li><li><p><strong>Tagging a catalog or schema cascades</strong> to everything inside it &#8212; the fastest way to fill a domain when a schema maps cleanly, and a blunt instrument when it doesn&#8217;t.</p></li><li><p>Browsing a parent domain rolls up its subdomains, but only shows what the viewer is already permitted to see.</p></li><li><p>Domains are <strong>account-level</strong> (governed tags replicate globally) while Discover is enabled per workspace. A single domain can even span <strong>multiple metastores</strong> &#8212; it isn&#8217;t tied to one. Two consequences: never put sensitive text in domain names or descriptions, and expect the same domain to look different across workspaces.</p></li><li><p>There&#8217;s a ceiling: <strong>300 domains/subdomains per account</strong>, and each also counts against the account&#8217;s 1,000 governed-tag limit (at the time of writing).</p></li><li><p><strong>Domains confer no access.</strong> Owner and Contact are metadata only.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ixUb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ixUb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png 424w, https://substackcdn.com/image/fetch/$s_!ixUb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png 848w, https://substackcdn.com/image/fetch/$s_!ixUb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png 1272w, https://substackcdn.com/image/fetch/$s_!ixUb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!ixUb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png" width="1456" height="1121" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1121,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!ixUb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png 424w, https://substackcdn.com/image/fetch/$s_!ixUb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png 848w, https://substackcdn.com/image/fetch/$s_!ixUb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png 1272w, https://substackcdn.com/image/fetch/$s_!ixUb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92acc56b-f2ce-4278-b758-c06ecf6796ff_1600x1232.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><em>Figure 3. Why each domain is its own tag key.</em></p><p><strong>The tradeoff of one key per domain.</strong> Every domain and subdomain uses up a governed tag (see the limit above), and governed tags are account-wide, so adopt a naming convention that keeps domain tags distinct from your other governed tags. Questions like &#8220;which domains is this asset in?&#8221; mean checking several keys rather than reading one value. And if you ever write ABAC policies against domain tags, you need a condition per domain &#8212; one more reason to keep access on its own tags (see &#8220;Domains and access control&#8221; below).</p><p>Managing domains needs curator permissions: account-level MANAGE DISCOVER, or MANAGE on a specific domain or subdomain. Day-to-day curation needs ASSIGN TAG on the domain&#8217;s tag plus APPLY TAG on the asset.</p><p></p><h2>Design principles for your domain taxonomy</h2><h3>Model the business, not the plumbing</h3><p>Base top-level domains on business capabilities, never source systems or pipelines. Source-system groupings feel natural to engineers and useless to everyone else: if your top-level domains read like a list of your ingestion connectors, start over.</p><p>So what <em>should</em> the top level look like? The four most common patterns we see with customers are below. Treat them as illustrative examples, not a fixed taxonomy &#8212; most real organizations blend them.</p><ul><li><p><strong>Function</strong> &#8212; grouped by type of work (Sales, Finance, HR). Owned by a cost-center lead with no standalone P&amp;L. Common in single-product or single-market orgs with one central data team.</p></li><li><p><strong>Region</strong> &#8212; grouped by geography (North America, EMEA, APAC). Owned by regional leadership, often with a regional P&amp;L, and usually driven by regulation or data residency. Common in banks, insurers, telcos, and multinationals.</p></li><li><p><strong>Brand</strong> &#8212; grouped by market-facing identity, such as the separate brands of a consumer-goods or automotive group. Owned by a brand team, usually with its own P&amp;L. Common in CPG, retail groups, hospitality, and automotive.</p></li><li><p><strong>Business Unit</strong> &#8212; a self-contained business (Retail, Commercial, Wealth, Markets) with a full P&amp;L, often its own ERP and data team. Common in conglomerates and diversified groups.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gq74!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gq74!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png 424w, https://substackcdn.com/image/fetch/$s_!gq74!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png 848w, https://substackcdn.com/image/fetch/$s_!gq74!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!gq74!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!gq74!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png" width="1456" height="1589" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1589,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!gq74!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png 424w, https://substackcdn.com/image/fetch/$s_!gq74!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png 848w, https://substackcdn.com/image/fetch/$s_!gq74!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!gq74!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa91dc1eb-9330-43cc-bb82-3c91798a0ccc_1466x1600.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><em>Figure 4. Four common ways to draw the top level. Most organizations blend them.</em></p><p>Rather than a decision tree, a few signals tell you which pattern dominates: separate P&amp;Ls lean Business Unit; hard data-residency lines lean Region; brand-owned P&amp;Ls lean Brand; one business on one platform leans Function. Whichever dominates, add a shared <strong>Corporate</strong> domain for consolidated data no single domain owns &#8212; group finance, enterprise reference data, cross-business KPIs.</p><h3>You have exactly two levels &#8212; spend them deliberately</h3><p>Keep 8&#8211;15 coarse top-level domains and add subdomains only where a domain is genuinely large or has distinct sub-owners. Where you do subdivide, find the <strong>greatest common denominator</strong> of how your domains naturally break down &#8212; the subdivision that holds up across most of them. That&#8217;s a modelling exercise, not a mechanical rule: the dimensions you <em>didn&#8217;t</em> choose for the top level don&#8217;t automatically become your subdomains. If you&#8217;re wishing for a third level, a top-level domain is really two.</p><p>Signs you&#8217;ve mis-sized: subdomains with one or two assets (that&#8217;s a tag, not a subdomain); a single &#8220;Analytics&#8221; domain that owns half the estate (too coarse); or a top level so broad that subdomains are doing all the real work.</p><h3>One clear owner per domain and subdomain</h3><p>Every domain needs a named, accountable owner, not a committee. Owner/Contact are metadata, not permissions &#8212; their job is answering &#8220;who do I ask?&#8221;, turning a Slack scavenger hunt into a one-click answer. If two teams both claim a domain, that&#8217;s a signal to split it into subdomains with distinct owners. If an asset sits in several domains, own it where it&#8217;s mastered; membership elsewhere is a reference that helps people find it, not co-ownership.</p><h3>Keep domains decoupled from physical layout</h3><p>A domain can span multiple catalogs and schemas with no data movement &#8212; design the taxonomy you want, then map assets into it. That decoupling is the feature&#8217;s superpower: it lets you present a clean business view without a risky migration.</p><h3>Design for discovery first</h3><p>Optimize for a person (or agent) browsing for data they don&#8217;t know exists: business-recognizable names, no jargon, shallow trees, and certification signals so people can tell the golden asset from the abandoned one. A good test: could a new analyst find &#8220;customer churn&#8221; data in under three clicks without asking anyone?</p><h3>Stable at the top, flexible below</h3><p>Top-level domains should map to durable capabilities; absorb reorgs in subdomains, which are cheaper to reshape. A volatile top level erodes trust and breaks bookmarks, dashboards, and mental models. Get names right from day one &#8212; <strong>a domain&#8217;s name is its tag key and can&#8217;t be changed later.</strong> You can edit a domain&#8217;s subtitle and description, but changing its name means creating a new domain, re-tagging assets, and deleting the old one.</p><p></p><h2>Mapping domains onto catalogs and schemas</h2><p>Three common layouts: <strong>schema-per-domain</strong> (a clean greenfield 1:1 mapping), <strong>catalog-per-business-unit</strong> (a top-level domain gathers schemas within one catalog, subdomains map to individual schemas), and <strong>domains spanning multiple catalogs</strong> (a &#8220;Customer&#8221; concept scattered across raw, curated, and data-science catalogs can be pulled into one business view without touching the physical layout).</p><p><strong>Rule of thumb:</strong> map domains to the gold/consumption layer first, then decide case-by-case whether raw/silver assets belong in the same domain or a separate &#8220;raw/engineering&#8221; domain. Tag a schema when it maps cleanly to one domain; tag individual assets when a schema mixes domains or holds scratch tables you don&#8217;t want surfaced.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pBX9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pBX9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png 424w, https://substackcdn.com/image/fetch/$s_!pBX9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png 848w, https://substackcdn.com/image/fetch/$s_!pBX9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png 1272w, https://substackcdn.com/image/fetch/$s_!pBX9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!pBX9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png" width="1456" height="1212" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1212,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!pBX9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png 424w, https://substackcdn.com/image/fetch/$s_!pBX9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png 848w, https://substackcdn.com/image/fetch/$s_!pBX9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png 1272w, https://substackcdn.com/image/fetch/$s_!pBX9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50250684-1f0b-40df-b3e8-65717b29b4ed_1600x1332.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><em>Figure 5. One estate, two views: domains pick consumption-ready assets from any catalog, with no data movement.</em></p><p>A domain is an overlay, not a remedy &#8212; confirm <strong>resolved identity</strong>, <strong>documented grain</strong>, and <strong>readable metadata</strong> on your gold layer before laying domains over it.</p><p></p><h2>Domains as the enabler of a Data Mesh on Databricks</h2><p>Domains are the Unity Catalog primitive that makes a domain-driven <strong>Data Mesh</strong> practical: each business domain gets an owner, a home for its data products, and a place in a shared discovery experience &#8212; without splitting the platform into separate stacks. Domain ownership, data-as-a-product, and federated governance map directly onto UC domains, grants, and ABAC, while Discover serves as the self-serve browsing index. The key insight: <strong>you get decentralized ownership without decentralized infrastructure</strong> &#8212; one metastore, one governance model, one discovery surface, with a business-shaped view on top.</p><p></p><h2>Domains and access control: what they do <em>not</em> do</h2><p>This is the most common misconception, so it&#8217;s worth stating plainly: <strong>domains are a curation and discovery layer, not an authorization mechanism.</strong> Assigning an asset to a domain does not grant or revoke access &#8212; users still need BROWSE/VIEW to see it listed and the underlying Unity Catalog privileges to query it. You also can&#8217;t make a domain private: there&#8217;s no setting that restricts a published domain to specific users or groups, and the only role you can assign on a domain is the curator role that lets someone manage it. (Before publishing, a draft domain is visible only to curators.) What each person sees <em>inside</em> a domain follows their permissions on the assets, not the domain &#8212; so don&#8217;t reach for domains to hide a business area. Owner/Contact remain metadata only.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O2HI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O2HI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png 424w, https://substackcdn.com/image/fetch/$s_!O2HI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png 848w, https://substackcdn.com/image/fetch/$s_!O2HI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png 1272w, https://substackcdn.com/image/fetch/$s_!O2HI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!O2HI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png" width="1456" height="1267" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1267,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!O2HI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png 424w, https://substackcdn.com/image/fetch/$s_!O2HI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png 848w, https://substackcdn.com/image/fetch/$s_!O2HI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png 1272w, https://substackcdn.com/image/fetch/$s_!O2HI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75f8bdbc-fce5-49d6-aadb-6785c7fae5ba_1600x1392.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><em>Figure 6. Domains help people find data; Unity Catalog decides who gets it.</em></p><p>Governed tags, including domain tags, <em>are</em> valid <a href="https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac">ABAC</a> attributes. <a href="https://docs.databricks.com/aws/en/data-governance/unity-catalog/filters-and-masks">Row filter and column mask</a> policies use them to <em>protect</em> data a user can already access; they don&#8217;t grant access to tables (yet). ABAC <a href="https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac/grant-policies">GRANT policies</a> can grant privileges by tag, but today only on models, model and MCP services, and (in Beta) agent services and skills. So for tables and views, domain membership doesn&#8217;t provision access.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.databricksters.com/subscribe?"><span>Subscribe now</span></a></p><p>If your access policies need a business-area attribute, model it as its <em>own</em> tag &#8212; a single key with controlled values &#8212; rather than reusing your discovery domain tags. The two shapes look similar but serve opposite purposes: one decides what a user can see, the other helps them find it. Keep access in grants and ABAC policies; use domains only to organize and surface what people can (or can request to) access.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gFzH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gFzH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png 424w, https://substackcdn.com/image/fetch/$s_!gFzH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png 848w, https://substackcdn.com/image/fetch/$s_!gFzH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png 1272w, https://substackcdn.com/image/fetch/$s_!gFzH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!gFzH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png" width="1456" height="1224" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1224,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!gFzH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png 424w, https://substackcdn.com/image/fetch/$s_!gFzH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png 848w, https://substackcdn.com/image/fetch/$s_!gFzH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png 1272w, https://substackcdn.com/image/fetch/$s_!gFzH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85022de0-9cd9-416e-8438-b2f92ccdeded_1600x1345.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><em>Figure 7. Keep discovery tags and policy tags separate.</em></p><p></p><h2>Operating model, ownership, and anti-patterns to avoid</h2><p>Stand up <strong>one high-value domain first</strong>, prove the pattern, then expand &#8212; don&#8217;t attempt an enterprise-wide rollout at once. Assign a named owner per domain/subdomain, govern top-level domain creation centrally, and delegate curation below. Link Pages and Metric Views <em>to</em> domain assets rather than duplicating descriptions, certify production-ready assets, and route feedback to the layer that actually owns the fault &#8212; a definition belongs in a Page, a measure in a Metric View, organization in the domain. Manage the taxonomy itself (domains, subdomains, owners) as code via the REST API or Terraform, and review it quarterly.</p><p>Common anti-patterns: mirroring the org chart instead of modeling capabilities; expecting domains to enforce access; reusing one tag for both discovery and ABAC policy; wishing for a third nesting level; source-system domains at the top (the exception: a pipeline team can keep a clearly labeled, source-aligned &#8220;Raw / Engineering&#8221; domain for the assets it owns, separate from the business taxonomy); one-asset subdomains; co-owning shared assets instead of picking a home; treating a domain as the semantic model itself; and placeholder names you&#8217;ll regret, since domains can&#8217;t be renamed.</p><p></p><h2>Getting started checklist</h2><ol><li><p>Confirm the foundation: resolved identity, documented grain, readable metadata on gold tables.</p></li><li><p>Inventory your gold/consumption assets.</p></li><li><p>Pick the dominant pattern &#8212; Function, Region, Brand, or Business Unit &#8212; plus a shared Corporate domain.</p></li><li><p>Draft 8&#8211;15 durable top-level domains with names you&#8217;ll keep.</p></li><li><p>Name an owner for each domain.</p></li><li><p>Add subdomains only where scale or ownership demands it.</p></li><li><p>Map assets into domains &#8212; tag schemas where they map cleanly, assets individually otherwise &#8212; and certify production-ready ones.</p></li><li><p>Operationalize one high-value domain end-to-end first, then expand.</p></li><li><p>Link Pages and Metric Views to domain assets.</p></li><li><p>Keep access in grants/ABAC, on its own tags.</p></li><li><p>Set a quarterly review cadence.</p></li></ol><p></p><h2>FAQ</h2><p><strong>Q: Is a Unity Catalog domain a new type of securable object?</strong></p><p>A: No. A domain is a governed tag that has been designated as a domain. It doesn&#8217;t change where data lives or create a new access boundary.</p><p><strong>Q: Can one asset belong to more than one domain?</strong></p><p>A: Yes. Because each domain is modelled as its own tag key rather than a value, membership is many-to-many &#8212; an asset like &#8220;Customer&#8221; can sit in both &#8220;Marketing&#8221; and &#8220;Risk&#8221; at once.</p><p><strong>Q: Do domains control who can see or query data?</strong></p><p>A: No. Domain membership and the Owner/Contact fields are metadata only. Access is governed entirely by Unity Catalog privileges and, where relevant, ABAC policies.</p><p><strong>Q: How many levels of subdomains can I create?</strong></p><p>A: Exactly one. A domain can have subdomains, but subdomains cannot have their own sub-subdomains. If you need a third level, your top-level domain probably needs to be split.</p><p><strong>Q: Can I rename a domain later?</strong></p><p>A: No. A domain&#8217;s name is its governed tag&#8217;s key, which is immutable. Renaming requires creating a new domain, re-tagging assets, and deleting the old one &#8212; so choose names you&#8217;ll keep.</p><p><strong>Q: Do domains require restructuring my catalogs and schemas?</strong></p><p>A: No. Domains are a logical overlay built on tags, so a single domain can span multiple catalogs and schemas without moving any data.</p><p></p><h2><strong>Documentation</strong></h2><p>Official Databricks docs for every feature mentioned in this post:</p><ul><li><p><strong>Genie Ontology &amp; Unity Catalog Semantics:</strong> <a href="https://docs.databricks.com/aws/en/genie/genie-ontology">Genie Ontology</a> &#183; <a href="https://docs.databricks.com/aws/en/uc-semantics">Unity Catalog Semantics</a> &#183; <a href="https://docs.databricks.com/aws/en/genie">Genie</a></p></li><li><p><strong>Domains and subdomains:</strong> <a href="https://docs.databricks.com/aws/en/uc-semantics/domains">Domains and subdomains</a> &#183; <a href="https://docs.databricks.com/api/domains/v1/domain">Domains REST API (Beta)</a> &#183; <a href="https://registry.terraform.io/providers/databricks/databricks/latest/docs/resources/domain">Terraform databricks_domain</a></p></li><li><p><strong>Pages:</strong> <a href="https://docs.databricks.com/aws/en/uc-semantics/pages">Pages</a></p></li><li><p><strong>Metric Views:</strong> <a href="https://docs.databricks.com/aws/en/uc-semantics/metric-views/">Metric views</a></p></li><li><p><strong>Governed tags:</strong> <a href="https://docs.databricks.com/aws/en/admin/governed-tags/">Governed tags</a> &#183; <a href="https://docs.databricks.com/aws/en/admin/governed-tags/manage-governed-tags">Manage governed tags</a> &#183; <a href="https://docs.databricks.com/aws/en/admin/governed-tags/manage-permissions">Governed tag permissions</a> &#183; <a href="https://docs.databricks.com/aws/en/admin/governed-tags/tag-propagation">Tag propagation</a></p></li><li><p><strong>Discover:</strong> <a href="https://docs.databricks.com/aws/en/discover/">Discover data</a> &#183; <a href="https://docs.databricks.com/aws/en/discover/discover-page">Discover page</a></p></li><li><p><strong>Certification:</strong> <a href="https://docs.databricks.com/aws/en/data-governance/unity-catalog/certify-deprecate-data">Certify and deprecate data</a></p></li><li><p><strong>Access control:</strong> <a href="https://docs.databricks.com/aws/en/data-governance/unity-catalog/access-control/privileges-reference">Unity Catalog privileges</a> &#183; <a href="https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac">ABAC</a> &#183; <a href="https://docs.databricks.com/aws/en/data-governance/unity-catalog/filters-and-masks">Row filters and column masks</a> &#183; <a href="https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac/grant-policies">ABAC GRANT policies</a></p></li><li><p><strong>MCP services, agent services, and skills:</strong> <a href="https://docs.databricks.com/aws/en/ai-gateway/govern-mcp-service">Govern MCP services</a> &#183; <a href="https://docs.databricks.com/aws/en/ai-gateway/agent-services">Register agents (agent services)</a> &#183; <a href="https://docs.databricks.com/aws/en/ai-gateway/govern-skills">Govern skills</a></p></li><li><p><strong>Limits:</strong> <a href="https://docs.databricks.com/aws/en/resources/limits">Resource limits</a></p></li></ul><h2><strong>Further reading</strong></h2><ul><li><p><a href="https://www.databricks.com/blog/operationalizing-genie-ontology-your-data-stack">Operationalizing Genie Ontology across your data stack</a> &#8212; the Databricks blog on where domains fit among the other layers of Genie Ontology, and how to roll them out.</p></li><li><p><a href="https://www.databricksters.com/p/making-uc-metric-views-discoverable">Making UC Metric Views Discoverable for Agents</a> &#8212; the Metric Views side of the semantic layer.</p></li><li><p><a href="https://www.databricksters.com/p/migrating-existing-dashboards-to-3e1">Migrating Existing Dashboards to Databricks AI/BI, Part 3: User Filters and Row-Level Security with Unity Catalog</a> &#8212; the access-control half that domains deliberately leave out.</p></li><li><p><a href="https://www.databricksters.com/p/why-your-information_schema-queries">Why Your information_schema Queries Are Killing the Metastore</a> &#8212; querying Unity Catalog metadata at scale.</p></li><li><p><a href="https://www.databricksters.com/p/stop-guessing-ai-agent-integrations">Stop Guessing AI Agent Integrations: The Databricks Decision Tree</a> &#8212; patterns for connecting agents to governed data.</p></li><li><p><a href="https://www.databricksters.com/p/multi-genie-slack-bot-production">Multi-Genie Slack Bot: Production-Grade Conversational Analytics in Slack</a> &#8212; putting Genie in front of business users.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><item><title><![CDATA[How Engineers Are Quietly Building Multi-Tenant Services on Zerobus Ingest]]></title><description><![CDATA[Architect a production-grade ingestion layer on Databricks using the four-tier pattern &#8212; producers, API services, Delta Lake, and downstream processing &#8212; without scaling worries]]></description><link>https://www.databricksters.com/p/how-engineers-are-quietly-building</link><guid isPermaLink="false">https://www.databricksters.com/p/how-engineers-are-quietly-building</guid><dc:creator><![CDATA[Canadian Data Guy]]></dc:creator><pubDate>Fri, 09 Oct 2026 01:35:39 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b332b16e-f98a-421c-bd27-350cfd308242_1396x742.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In our previous post, <a href="https://www.databricksters.com/p/databricks-zerobus-the-best-bus-is">Databricks Zerobus &#8212; The Best Bus Is No Bus</a>, we covered what Zerobus Ingest is, how it works under the hood, and why it&#8217;s a compelling alternative to Kafka when your sole destination is the Lakehouse. That post resonated &#8212; and the most common follow-up question I&#8217;ve gotten from principal architects and platform teams is:</p><blockquote><p><em>&#8220;Great, but can I build a managed service on top of it? And how do I do transformations before the data hits the table?&#8221;</em></p></blockquote><p>This post answers both questions in depth. If you&#8217;re a platform engineer, principal architect, or anyone designing a multi-tenant ingestion layer on Zerobus Ingest &#8212; this is for you.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><div><hr></div><h2>The Two Questions Engineers Ask</h2><p>When I sit down with customers who are evaluating Zerobus Ingest for production, the conversation almost always lands on two things:</p><ol><li><p><strong>&#8220;Can we do transformations on the fly?&#8221;</strong> &#8212; They want to scrub PII, normalize schemas, filter noise, or enrich records before they land in Delta.</p></li></ol><ol start="2"><li><p><strong>&#8220;Can we build a managed service on top of Zerobus?&#8221;</strong> &#8212; They&#8217;re building a platform for internal teams or external tenants, and they want Zerobus as the ingestion backbone.</p></li></ol><p>The short answer to both: <strong>yes, but not the way you might expect.</strong> Zerobus Ingest is intentionally simple &#8212; it does one thing extremely well (durable, high-throughput 12+ Gb/Second writes to Delta), and it does not try to be a transformation engine. That&#8217;s a feature, not a limitation.</p><div><hr></div><h2>Why Zerobus Doesn&#8217;t Do Transformations (And Why That&#8217;s Good)</h2><p>Let me be direct: <strong>Zerobus Ingest does not offer native transformations.</strong> </p><p>This is a deliberate design choice. Zerobus is built in Rust for raw speed &#8212; WAL-durable ACKs in ~150ms, data queryable in Delta within ~5 seconds, 100 MB/s per stream, 10+ GB/s per table. Adding a transformation layer inside the ingestion path would compromise that performance and add complexity to a service whose entire value proposition is simplicity.</p><p>But here&#8217;s the thing: <strong>you don&#8217;t need Zerobus to do transformations.</strong> You need a <em>tiered transformation strategy</em> &#8212; and if you&#8217;re coming from a Kafka architecture, you probably already have the pieces in place.</p><div><hr></div><h2>The Tiered Transformation Architecture</h2><p>This is the pattern I recommend to  customers building on Zerobus. </p><h3>The Four Tiers</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3ERz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3ERz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png 424w, https://substackcdn.com/image/fetch/$s_!3ERz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png 848w, https://substackcdn.com/image/fetch/$s_!3ERz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png 1272w, https://substackcdn.com/image/fetch/$s_!3ERz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!3ERz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png" width="1266" height="346" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:346,&quot;width&quot;:1266,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71508,&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://www.databricksters.com/i/219491314?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.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_!3ERz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png 424w, https://substackcdn.com/image/fetch/$s_!3ERz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png 848w, https://substackcdn.com/image/fetch/$s_!3ERz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png 1272w, https://substackcdn.com/image/fetch/$s_!3ERz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9500d88e-b299-4e2c-bd7c-fc6293a6a85f_1266x346.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><strong>TIER 1: PRODUCERS</strong> Mobile apps, web apps, IoT devices, APIs. Raw events: unvalidated, may contain PII, mixed formats.</p><p><strong>TIER 2: YOUR INGESTION API SERVICE</strong> FastAPI / Flask / custom Go or Rust micro-service.</p><p>This is where in-flight transforms happen:</p><ul><li><p>&#9989; PII scrubbing / anonymization</p></li><li><p>&#9989; IP &#8594; Geo mapping (database lookups)</p></li><li><p>&#9989; Compliance flags (privacy service calls)</p></li><li><p>&#9989; Format conversion (XML&#8594;JSON, CSV&#8594;JSON)</p></li><li><p>&#9989; Tenant identification &amp; tagging</p></li></ul><p>Latency budget: milliseconds per record. Then calls: stream.ingest_record_offset({cleaned_record})</p><p><strong>TIER 3: ZEROBUS INGEST</strong> Databricks-managed, serverless.</p><ul><li><p>Schema validation against Delta table</p></li><li><p><strong>Durable ACK in ~150ms</strong></p></li><li><p><strong>Queryable in ~5 seconds</strong></p></li><li><p>Auto-compaction via Predictive Optimization</p></li></ul><p><strong>TIER 4: LAKEHOUSE (Downstream)</strong></p><ul><li><p>Bronze &#8594; Silver: Deduplication, type casting</p></li><li><p>Silver &#8594; Gold: Multi-stage enrichment, joins</p></li><li><p>Complex aggregations, ML feature engineering</p></li><li><p>SCD Type 2, slowly changing dimensions</p></li><li><p>Tools: Lakeflow Declarative Pipelines / DLT / dbt</p></li></ul><h3>Why This Works</h3><p>The key insight is that <strong>Tier 2 already exists in most architectures.</strong> If you&#8217;re currently using Kafka, you have an ingestion API service that receives events from producers and forwards them to Kafka. That same service can call the Zerobus SDK instead of the Kafka producer &#8212; and you can add your lightweight transforms there.</p><ul><li><p><strong>In-Flight (Tier 2):</strong> Record-level transforms via API calls to rules-based services and database lookups (IP-to-Geo mapping, privacy flags, anonymization)</p></li><li><p><strong>Compliance &amp; Privacy:</strong> Anonymization and pseudonymization happen <em>before</em> storage &#8212; critical for GDPR/CCPA</p></li><li><p><strong>Downstream (Tier 4):</strong> Deduplication from Bronze to Silver, multi-stage enrichment from Silver to Gold</p></li></ul><p>The beauty of this pattern is that your transformation logic is <strong>yours</strong> &#8212; you own it, you version it, you test it. Zerobus stays fast and simple.</p><h3>Reference Implementation: Zerobus Ingest Station</h3><p>Databricks has published a reference implementation called <strong>Zerobus Ingest Station</strong> &#8212; a FastAPI-based container service that wraps the Zerobus SDK to create customizable ingestion endpoints. This is exactly the Tier 2 pattern:</p><ul><li><p>Define validation logic and transformations before pushing records to Zerobus</p></li><li><p>Ideal for external partners, IoT devices, or REST integrations</p></li><li><p>Deploy on Databricks Apps, Kubernetes, or any container runtime</p></li></ul><p>Check it out: <a href="https://github.com/hntd187/zerobus-examples">Zerobus Ingest Station on GitHub</a></p><div><hr></div><h2>Building a Multi-Tenant Managed Service</h2><p>Now let&#8217;s talk about the second question: building a managed service. This is the pattern I&#8217;ve been recommending to healthcare, manufacturing, and SaaS platform customers.</p><h3>The Architecture</h3><p>Your managed service platform sits in front of Databricks:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q6cm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q6cm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png 424w, https://substackcdn.com/image/fetch/$s_!Q6cm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png 848w, https://substackcdn.com/image/fetch/$s_!Q6cm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png 1272w, https://substackcdn.com/image/fetch/$s_!Q6cm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q6cm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png" width="1266" height="650" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:650,&quot;width&quot;:1266,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:118545,&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://www.databricksters.com/i/219491314?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.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_!Q6cm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png 424w, https://substackcdn.com/image/fetch/$s_!Q6cm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png 848w, https://substackcdn.com/image/fetch/$s_!Q6cm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png 1272w, https://substackcdn.com/image/fetch/$s_!Q6cm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a0a6e6-acc9-44a4-92f4-f621535659ed_1266x650.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>Zerobus Supports Both Row-Level AND Bulk Ingestion</h2><p>One misconception I keep hearing: &#8220;Zerobus is only for row-by-row ingestion.&#8221; <strong>Not true.</strong> Here&#8217;s the full picture:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GLef!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GLef!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png 424w, https://substackcdn.com/image/fetch/$s_!GLef!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png 848w, https://substackcdn.com/image/fetch/$s_!GLef!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png 1272w, https://substackcdn.com/image/fetch/$s_!GLef!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!GLef!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png" width="841" height="246" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d4457ff3-1d17-4500-8136-2156499078d0_841x246.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:246,&quot;width&quot;:841,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38398,&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://www.databricksters.com/i/219491314?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.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_!GLef!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png 424w, https://substackcdn.com/image/fetch/$s_!GLef!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png 848w, https://substackcdn.com/image/fetch/$s_!GLef!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png 1272w, https://substackcdn.com/image/fetch/$s_!GLef!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4457ff3-1d17-4500-8136-2156499078d0_841x246.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>Arrow Flight is the key for bulk scenarios:</p><ul><li><p>Sends entire RecordBatch (thousands of rows) per call</p></li><li><p>Splits large batches into smaller transport messages automatically</p></li><li><p>Best when your producer already works with columnar data (Polars, DataFusion, pyarrow)</p></li><li><p>Supports ZSTD compression for improved throughput</p></li></ul><p>If your managed service needs to handle both real-time events and periodic bulk loads, Zerobus has you covered with a single endpoint.</p><div><hr></div><h2>Regional Endpoints: There Is No Multi-Region Endpoint</h2><p>This catches people off guard, so let me be clear: <strong>Zerobus endpoints are regional.</strong> There is no global or multi-region endpoint.</p><p>Your producers call the endpoint in the region where your <strong>workspace</strong> (and Delta tables) reside. Cross-region calls work but incur cloud provider egress charges. For best throughput, keep producer and endpoint in the same region.</p><div><hr></div><h2>What You Need to Call Zerobus</h2><p>For your managed service&#8217;s Tier 2 layer, here are the parameters:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R6VG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R6VG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png 424w, https://substackcdn.com/image/fetch/$s_!R6VG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png 848w, https://substackcdn.com/image/fetch/$s_!R6VG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png 1272w, https://substackcdn.com/image/fetch/$s_!R6VG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!R6VG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png" width="831" height="365" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:365,&quot;width&quot;:831,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:43029,&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://www.databricksters.com/i/219491314?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.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_!R6VG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png 424w, https://substackcdn.com/image/fetch/$s_!R6VG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png 848w, https://substackcdn.com/image/fetch/$s_!R6VG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png 1272w, https://substackcdn.com/image/fetch/$s_!R6VG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9607f9d7-4827-4272-a5d3-3bb9efd387dd_831x365.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><strong>Critical permission gotcha:</strong> The service principal needs explicit grants &#8212; USE CATALOG, USE SCHEMA, SELECT, and MODIFY on the target table. <strong>ALL_PRIVILEGES does NOT work</strong> and will give you an invalid_authorization_details error. </p><div><hr></div><h2>The Honest Part: When NOT to Use This Pattern</h2><p>Staying true to the spirit of my original post &#8212; here&#8217;s when this architecture doesn&#8217;t fit:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rW9D!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rW9D!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png 424w, https://substackcdn.com/image/fetch/$s_!rW9D!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png 848w, https://substackcdn.com/image/fetch/$s_!rW9D!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png 1272w, https://substackcdn.com/image/fetch/$s_!rW9D!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!rW9D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png" width="833" height="414" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:414,&quot;width&quot;:833,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64018,&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://www.databricksters.com/i/219491314?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.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_!rW9D!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png 424w, https://substackcdn.com/image/fetch/$s_!rW9D!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png 848w, https://substackcdn.com/image/fetch/$s_!rW9D!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png 1272w, https://substackcdn.com/image/fetch/$s_!rW9D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4c640cd-bb23-43e3-bb89-6f6c772c8291_833x414.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>Customer Proof Points</h2><p>This isn&#8217;t theoretical. Here are real customers stories:</p><p><strong><a href="https://www.databricks.com/customers/beusa-energy/zerobus-ingest">Beusa Energy (Oil &amp; Gas IoT)</a>:</strong> &gt;99% cost reduction, ~3 seconds end-to-end latency, 22M+ rows/day from 6,000+ devices. <em>&#8220;Zerobus Ingest saved us a lot of money, simplified our stack, and put us on a managed path forward.&#8221;</em></p><p><strong><a href="https://www.databricks.com/customers/joby/lakeflow-connect">Joby Aviation (eVTOL Manufacturing)</a>:</strong> Streams gigabytes per minute from manufacturing sites. Reduced telemetry resolution latency from days to minutes. Custom on-premises forwarding agents push directly to Zerobus.</p><p><strong><a href="https://www.databricks.com/customers/toyota/lakeflow-connect">Toyota Motor Corporation (Factory IoT):</a></strong> Detects overheating factory conditions in minutes instead of hours. Uses Soracom Beam for global IoT connectivity &#8594; Zerobus &#8594; Delta.</p><div><hr></div><h2>Getting Started</h2><p>If you want to build this pattern yourself:</p><ol><li><p><strong>Start with the docs:</strong> <a href="https://docs.databricks.com/aws/en/ingestion/zerobus-overview">Zerobus Ingest Overview</a></p></li><li><p><strong>Try the Zerobus Ingest Station:</strong> <a href="https://github.com/hntd187/zerobus-examples">GitHub</a> &#8212; a ready-made Tier 2 service</p></li><li><p><strong>Read the deep dive:</strong> <a href="https://community.databricks.com/t5/technical-blog/deep-dive-on-zerobus-ingest-now-ga/ba-p/148385">Deep Dive on Zerobus Ingest (Now GA)</a></p></li><li><p><strong>See it at scale:</strong> <a href="https://www.databricks.com/blog/ingesting-milky-way-petabyte-scale-zerobus-ingest">Ingesting the Milky Way: Petabyte-Scale</a></p></li><li><p><strong>Build an end-to-end app:</strong> <a href="https://www.databricks.com/blog/building-near-real-time-application-zerobus-ingest-and-lakebase">Near Real-Time App with Zerobus + Lakebase</a></p></li></ol><div><hr></div><h2>Conclusion</h2><p>Building a managed service on Zerobus Ingest is not only possible &#8212; it&#8217;s the pattern Databricks designed for. Your service owns the API gateway, authentication, rate limiting, and lightweight transformations. Zerobus owns durable, high-throughput writes to governed Delta tables. The Lakehouse owns deep enrichment downstream.</p><p>The architecture is simple because Zerobus is simple. And sometimes the best architecture isn&#8217;t the most sophisticated &#8212; it&#8217;s the one that solves your problem.</p><p>Thanks for reading!</p><div><hr></div><p><em>Jitesh Soni is a Specialist Solutions Architect at Databricks and the author of <a href="https://www.canadiandataguy.com">canadiandataguy.com</a>. He writes about streaming, data engineering, and the things that break at 3 AM.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><item><title><![CDATA[How to get more from the same hardware with Spark Declarative Pipelines?]]></title><description><![CDATA[Inside Microbatch Pipelining: How Serverless Pipelines Plan, Execute, and Commit Batches Simultaneously]]></description><link>https://www.databricksters.com/p/how-to-get-more-from-the-same-hardware</link><guid isPermaLink="false">https://www.databricksters.com/p/how-to-get-more-from-the-same-hardware</guid><dc:creator><![CDATA[Canadian Data Guy]]></dc:creator><pubDate>Wed, 07 Oct 2026 14:55:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/yQFs7BNpyv8" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;re running continuous streaming on serverless Lakeflow, there&#8217;s a feature working behind the scenes that most people don&#8217;t know exists. It&#8217;s called <strong>Microbatch Pipelining</strong>, it&#8217;s enabled by default, and it fundamentally changes how your streaming pipeline processes data.</p><p>It overlaps the Plan, Execute, and Commit phases across multiple batches simultaneously, shrinking the gap between batches to near zero and delivering meaningfully fresher data. It&#8217;s on by default. It&#8217;s serverless only. And it&#8217;s the reason your continuous pipeline on serverless Lakeflow get more from the same hardware.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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>This post breaks down what it does, how it works, and why it matters for data freshness.</p><div id="youtube2-yQFs7BNpyv8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;yQFs7BNpyv8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/yQFs7BNpyv8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2>How Streaming Normally Works: One Batch at a Time</h2><p>Every Spark Structured Streaming pipeline processes data in microbatches. Each microbatch goes through three phases:</p><ol><li><p><strong>Plan</strong> &#8212; Determine the next slice of data to process (read offsets from the source, discover new files, define batch boundaries)</p></li><li><p><strong>Execute</strong> &#8212; Run the Spark SQL transformations on the executors</p></li><li><p><strong>Commit</strong> &#8212; Write the results to Delta and persist the checkpoint</p></li></ol><p>On classic compute, these phases run <strong>strictly sequentially</strong>. Batch 1 must complete all three phases &#8212; Plan, Execute, Commit &#8212; before Batch 2 even begins planning.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hv-o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hv-o!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png 424w, https://substackcdn.com/image/fetch/$s_!hv-o!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png 848w, https://substackcdn.com/image/fetch/$s_!hv-o!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png 1272w, https://substackcdn.com/image/fetch/$s_!hv-o!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!hv-o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png" width="844" height="513" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:513,&quot;width&quot;:844,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;smunigati_0-1785269128660.png&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="smunigati_0-1785269128660.png" title="smunigati_0-1785269128660.png" srcset="https://substackcdn.com/image/fetch/$s_!hv-o!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png 424w, https://substackcdn.com/image/fetch/$s_!hv-o!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png 848w, https://substackcdn.com/image/fetch/$s_!hv-o!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png 1272w, https://substackcdn.com/image/fetch/$s_!hv-o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25cc5132-729f-4107-b6a3-c9b5e32f3a23_844x513.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 each batch takes ~1.45 seconds end-to-end, processing 4 batches takes ~5.8 seconds. During the Plan and Commit phases, your executors are largely idle &#8212; waiting for the driver to finish its work before they get the next batch to process.</p><h2>What Microbatch Pipelining Changes</h2><p>On serverless Lakeflow pipelines, <strong>Microbatch Pipelining</strong> is enabled by default. Instead of waiting for Batch 1 to finish entirely, the engine determines the data boundaries for Batch 2 while Batch 1 is still executing. While Batch 2 executes, Batch 3 is already planning. Up to <strong>4 microbatches can be in flight at the same time</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_!G5po!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G5po!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png 424w, https://substackcdn.com/image/fetch/$s_!G5po!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png 848w, https://substackcdn.com/image/fetch/$s_!G5po!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!G5po!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!G5po!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png" width="1456" height="886" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:886,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3474950,&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://www.databricksters.com/i/219206664?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.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_!G5po!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png 424w, https://substackcdn.com/image/fetch/$s_!G5po!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png 848w, https://substackcdn.com/image/fetch/$s_!G5po!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!G5po!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d677053-112a-4941-aa29-5c090986f5ef_2736x1664.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 key insight is that the phases of different batches <strong>overlap</strong> &#8212; the planning of the next batch runs concurrently with the execution of the current one, and commits are staggered so they don&#8217;t conflict.</p><h2>How the Overlap Actually Works</h2><p>Each streaming query gets its own thread pool (prefixed microbatch-runner) that manages up to 4 concurrent batches. The overlap isn&#8217;t random &#8212; there are synchronization points between batches to maintain correctness:</p><ul><li><p><strong>Batch N+1 can begin planning</strong> as soon as Batch N&#8217;s offsets are committed to the write-ahead log (after the Plan phase), even while Batch N is still executing.</p></li><li><p><strong>Batch N+1 can begin executing</strong> once its own planning is complete, even if Batch N hasn&#8217;t committed yet.</p></li><li><p><strong>Commits are serialized</strong> &#8212; Batch N+1 waits for Batch N&#8217;s commit to complete before committing its own results. This preserves exactly-once semantics and checkpoint ordering.</p></li></ul><p>This means the expensive Execute phase &#8212; where the actual data transformations happen on the executors &#8212; runs in parallel across multiple batches, while the lightweight Plan and the ordering-sensitive Commit phases are carefully coordinated.</p><h2>What This Means for Data Freshness</h2><p>The practical impact is significant. With sequential execution, your data freshness is bounded by the full batch cycle time &#8212; if each batch takes 10 seconds, new data waits up to 10 seconds before the next batch even starts processing it.</p><p>With pipelining, the next batch starts planning almost immediately after the current batch begins executing. Data that arrives during Batch 1&#8217;s execution is picked up by Batch 2&#8217;s planning phase &#8212; which is already running. The effective freshness improves because the <strong>gap between batches shrinks to near zero</strong>.</p><p>In a <a href="https://community.databricks.com/t5/technical-blog/triggered-vs-continuous-mode-a-deep-dive-into-serverless/ba-p/164327">benchmark on serverless Lakeflow pipelines</a>, the Silver query showed:</p><ul><li><p>Average batch duration: <strong>2,242 ms</strong> (against a 1-second trigger interval)</p></li><li><p>Batch overlap observed in <strong>99.7%</strong> of batches</p></li><li><p>Up to <strong>3 microbatches in flight simultaneously</strong></p></li></ul><p>Nearly every batch was pipelined with its neighbors. The pipeline was continuously processing overlapping batches rather than waiting in a sequential queue.</p><h2>Key Details</h2><ul><li><p><strong>Serverless only.</strong> Microbatch Pipelining is enabled by default on serverless Lakeflow pipelines. It is not available on classic compute and there are no plans to enable it there.</p></li><li><p><strong>Up to 4 concurrent batches.</strong> The default maximum is 4 in-flight microbatches per streaming query, controlled by spark.databricks.streaming.execution.pipelined.maxInflightBatches.</p></li><li><p><strong>Concurrency compounds.</strong> For a pipeline with N streaming tables, each table can have up to 4 concurrent batches. A pipeline with 30 streaming tables could have up to 120 concurrent batches across all queries &#8212; all sharing the same driver.</p><blockquote><p><strong>You can turn it off.</strong> If your workload doesn&#8217;t benefit from pipelining (e.g., the pipeline is already keeping up with the source rate), you can disable it:</p><p></p><p>spark.databricks.streaming.forceDisablePipelinedExecution = true</p><p></p><p>This reverts to sequential execution.</p></blockquote></li></ul><div id="youtube2-DDqMNMF57P8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;DDqMNMF57P8&quot;,&quot;startTime&quot;:&quot;2605&quot;,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/DDqMNMF57P8?start=2605&amp;rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2>When Pipelining Helps Most</h2><p>Microbatch Pipelining delivers the biggest freshness improvement when:</p><ul><li><p><strong>Batch duration exceeds the trigger interval.</strong> If your trigger interval is 1 second but each batch takes 3 seconds, sequential execution falls behind. Pipelining keeps up by overlapping batches.</p></li><li><p><strong>The pipeline has many flows.</strong> More streaming tables means more opportunities for parallel batch execution across queries.</p></li><li><p><strong>The workload is stateless.</strong> Stateless transformations (appends, filters, maps) pipeline cleanly. Stateful operations (aggregations, deduplication) have additional synchronization requirements.</p></li></ul><p>It helps less when:</p><ul><li><p><strong>The pipeline is already idle between batches.</strong> If each batch finishes well within the trigger interval, there&#8217;s nothing to overlap &#8212; pipelining has no work to pipeline.</p></li><li><p><strong>The driver is memory-constrained.</strong> Each in-flight batch holds execution state on the driver. With many streaming tables and 4 concurrent batches each, driver memory pressure can increase. For pipelines with 30+ flows, monitor driver memory if pipelining is enabled.</p></li></ul><h2>FAQ</h2><p><strong>Q: Is Continuous mode always better than Triggered mode?</strong><br><strong>A:</strong> No. Triggered mode is often preferable for periodic or bursty data when minutes or hours of latency are acceptable. Continuous mode is more appropriate for steady arrivals and low-latency requirements.</p><p><strong>Q: What is stream pipelining?</strong><br><strong>A:</strong> Stream pipelining allows eligible queries in Serverless Spark Declarative Pipelines to overlap work from successive micro-batches rather than requiring strictly serial end-to-end completion.</p><p><strong>Q: Is micro-batch pipelining the same as Spark task parallelism?</strong><br><strong>A:</strong> No. Task parallelism occurs within one micro-batch. Stream pipelining allows work associated with different micro-batches to overlap.</p><p><strong>Q: Does pipelining eligibility guarantee overlapping batches?</strong><br><strong>A:</strong> No. A query may be eligible but show no overlap when its batch duration is shorter than its trigger interval, as occurred with the Gold query.</p><p><strong>Q: How should I select a trigger interval?</strong><br><strong>A:</strong> Choose an interval that meets the workload&#8217;s freshness SLO while avoiding sustained backlog or instability. Do not reduce the interval solely to produce concurrent batches.</p><h3>References</h3><ol><li><p><code>https://community.databricks.com/t5/technical-blog/triggered-vs-continuous-mode-a-deep-dive-into-serverless/ba-p/164327</code> <em>Triggered vs. Continuous Mode: A Deep Dive into Serverless Lakeflow Spark Declarative Pipelines</em></p></li><li><p><strong>Configure a serverless pipeline</strong> &#8212; this is the official doc that mentions Stream Pipelining by name <code>https://learn.microsoft.com/en-us/azure/databricks/ldp/serverless/</code> <em>&#8220;Stream pipelining: To improve utilization, throughput, and latency for streaming data workloads such as data ingestion, microbatches are pipelined... enabled by default in serverless pipelines.&#8221;</em></p></li><li><p><strong>Streaming on serverless compute</strong> &#8212; confirms stream pipelining is on by default, has the use-case table <code>https://learn.microsoft.com/en-us/azure/databricks/compute/serverless/streaming/</code> <em>&#8220;Stream pipelining is enabled by default in serverless Lakeflow pipelines. Microbatches run concurrently rather than sequentially.&#8221;</em></p></li><li><p><strong>Pipeline event log</strong> &#8212; for readers who want to measure freshness using <code>flow_progress</code> and <code>stream_progress</code> events <code>https://learn.microsoft.com/en-us/azure/databricks/ldp/monitor-event-logs/</code></p></li><li><p><strong>Best practices for Lakeflow pipelines</strong> &#8212; triggered vs continuous mode guidance <code>https://learn.microsoft.com/en-us/azure/databricks/ldp/best-practices/index/</code></p></li><li><p><strong>Configure Structured Streaming trigger intervals</strong> &#8212; explains the trigger model that MBP builds on <code>https://learn.microsoft.com/en-us/azure/databricks/structured-streaming/triggers/</code></p></li><li><p><strong>Monitor pipelines</strong> &#8212; overview of all monitoring options (UI, event log, system tables) <code>https://learn.microsoft.com/en-us/azure/databricks/ldp/observability/</code></p></li></ol><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><item><title><![CDATA[Metrics Always Outlive Their Sources]]></title><description><![CDATA[We build streaming jobs as if their inputs are permanent. They never are. Source evolution in Databricks, lets the job, and the metric it computes, keep running while the sources underneath changes]]></description><link>https://www.databricksters.com/p/metrics-always-outlive-their-sources</link><guid isPermaLink="false">https://www.databricksters.com/p/metrics-always-outlive-their-sources</guid><dc:creator><![CDATA[Eric Marnadi]]></dc:creator><pubDate>Fri, 02 Oct 2026 14:55:30 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/2fa79356-9bf8-45f8-9a73-7442a6010a97_1018x764.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>TL;DR / Key Takeaways</h2><ul><li><p>Structured Streaming checkpoints traditionally identify sources by position, so changing the source list can require a fresh checkpoint and discarded state.</p></li><li><p><strong>Source evolution</strong> tracks offsets by stable <code>.name()</code> values instead.</p></li><li><p><strong>On-demand state repartitioning</strong> (Public Preview, RocksDB) can resize state partitions on restart.</p></li><li><p>This Ethereum archive-to-live test preserved one checkpoint and query id through source addition, source removal, and state repartitioning without rereading archive rows or losing keys.</p></li><li><p>Name every source when creating the checkpoint, and plan for tombstoned names and retained state.</p></li></ul><h2><strong>Problem: We hope for permanent sources, and life give us transient ones</strong></h2><p>Think about what a streaming job is actually for. It maintains a metric: orders per region per minute, active sessions per tenant, fraud score per card, blocks per proposer per window. That metric has consumers who expect it to be continuous. It has a state store carrying every open window and every in-flight key. It is the thing the business depends on.</p><p>Now think about what feeds it. A Kafka cluster that will be migrated next quarter. A vendor feed on a contract that ends in March. A region launching in six months. An S3 archive that exists only until the live feed catches up. A tenant who churns. None of these are permanent, and everyone knows it on the day the job is written.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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>Yet the checkpoint treats the source list as permanent. Structured Streaming identifies sources by position&#8212;0, 1, 2&#8212;and the offset log is positional too. Add, remove, or reorder a source and the query refuses to start; for a stateful job, the only fix was a fresh checkpoint&#8212;discarding state and replaying history while consumers see a gap or a double count.</p><p>Teams worked around that in three ways: <strong>replay inside a maintenance window</strong> (duplicates and downtime), <strong>one job per source</strong> (the metric assembled downstream from N partial metrics), or <strong>a bus in the middle</strong> (an extra system /table whose only job is making the source list look permanent).</p><p>Each workaround compensates for the engine; source evolution removes the need&#8212;the job and its state stay put, and sources become what they always were: replaceable.</p><div class="callout-block" data-callout="true"><p>Each of those is an architecture decision made to compensate for the engine. Source evolution removes the need for it: the job and its state stay put, and the sources become what they always were, replaceable.</p></div><h2>What changed: named sources and resizable state</h2><p>With source evolution enabled, every streaming source carries a stable, user-defined name via <code>.name()</code>, and the checkpoint tracks offsets by that name instead of by position. Three operations become safe across restarts on the same checkpoint:</p><ul><li><p><strong>Reorder</strong> sources. Each one resumes from its own last committed offset.</p></li><li><p><strong>Add</strong> a source. The new source starts from its beginning; existing sources continue where they stopped.</p></li><li><p><strong>Remove</strong> a source. Its name is tombstoned in the checkpoint and cannot be reused, which protects you from silently rewinding a feed.</p></li></ul><p>There is a second thing a checkpoint used to freeze: the number of state partitions, set by <code>spark.sql.shuffle.partitions</code> on the day the checkpoint was born. A backfill wants a wide cluster and many partitions; a live feed of a few records per second wants a small node and a handful. <a href="https://docs.databricks.com/aws/en/structured-streaming/state-repartitioning">On-demand state repartitioning</a> (Public Preview, RocksDB) unfreezes that too. Together, the two features cover the whole lifecycle: change what feeds the query, then right-size what runs it, without ever starting over.</p><h2><strong>The proof: one checkpoint through a full ingestion lifecycle</strong></h2><p>We wanted a workload where losing state would hurt, on data anyone can reproduce: AWS publishes the entire Ethereum chain as date-partitioned Parquet in a public S3 bucket, and public JSON-RPC endpoints serve the live tip with no signup. The query counts blocks per 5-minute window, per source and proposer (<code>miner</code>), with a 2-day watermark, update mode, a <code>foreachBatch</code> MERGE into Delta, and RocksDB state&#8212;run through five steps on one checkpoint, as tasks of one Databricks job, scored from the run's own <code>StreamingQueryProgress</code> </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DCKB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DCKB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png 424w, https://substackcdn.com/image/fetch/$s_!DCKB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png 848w, https://substackcdn.com/image/fetch/$s_!DCKB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png 1272w, https://substackcdn.com/image/fetch/$s_!DCKB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!DCKB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png" width="1400" height="636" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:636,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Keys in the state store per microbatch on a log scale across four phases, with the state partition count stepping from 200 down to 8&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="Keys in the state store per microbatch on a log scale across four phases, with the state partition count stepping from 200 down to 8" title="Keys in the state store per microbatch on a log scale across four phases, with the state partition count stepping from 200 down to 8" srcset="https://substackcdn.com/image/fetch/$s_!DCKB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png 424w, https://substackcdn.com/image/fetch/$s_!DCKB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png 848w, https://substackcdn.com/image/fetch/$s_!DCKB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png 1272w, https://substackcdn.com/image/fetch/$s_!DCKB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91c1b82-5301-4703-bd68-8e307fc90e90_1400x636.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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">Drawn from the run&#8217;s own <code>StreamingQueryProgress</code> records, unedited. Red: keys in RocksDB after each batch. Blue: state store partitions. Four phases, one query id.</figcaption></figure></div><h3><strong>Step 1: name the sources</strong></h3><p>For a newly created compatible query, the following two configs plus a stable, unique <code>.name()</code> on every source enable supported source-list evolution. This cannot be enabled retroactively on an existing checkpoint; removed names are permanently tombstoned; and support requires Databricks Runtime 18.2 or later.</p><pre><code><code>spark.conf.set("spark.sql.streaming.queryEvolution.enableSourceEvolution", "true")
spark.conf.set("spark.sql.streaming.offsetLog.formatVersion", "2")

archive = (spark.readStream.format("parquet").schema(BLOCK_SCHEMA)
           .option("maxFilesPerTrigger", 365)
           .name("s3_backfill")
           .load(landing))
</code></code></pre><blockquote><p><strong>Naming requires a fresh checkpoint, and it is one-way.</strong> You cannot enable source evolution on a checkpoint created without it, and you cannot turn it off later. The practical consequence: name every source on day one, including the ones you are certain will never change.</p></blockquote><div class="pullquote"><p><strong>If a fresh query fails with </strong><code>NAMED_SOURCES_REQUIRE_OFFSET_LOG_V2</code><strong>,</strong> set <code>spark.sql.streaming.offsetLog.formatVersion</code> to <code>2</code>. Note that the message text refers to the key as <code>offsetLog.version</code>; the key that takes effect on Databricks Runtime 18.3 is <code>formatVersion</code>.</p></div><h3><strong>Step 2: backfill the archive on a cluster that turns itself off</strong></h3><p>Backfill is the expensive part: We used 8 workers, Spark&#8217;s default 200 shuffle partitions, and <code>trigger(availableNow=True)</code> to read everything and stop.</p><pre><code><code>(events.writeStream
       .option("checkpointLocation", checkpoint)
       .trigger(availableNow=True)  # drain, then stop paying
       .foreachBatch(upsert_into_delta)
       .start())</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jx44!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jx44!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png 424w, https://substackcdn.com/image/fetch/$s_!jx44!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png 848w, https://substackcdn.com/image/fetch/$s_!jx44!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png 1272w, https://substackcdn.com/image/fetch/$s_!jx44!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!jx44!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png" width="1400" height="644" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:644,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Backfill receipt in notebook output: 26,086,569 blocks at 200 state partitions in 301.9 seconds of batch time&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="Backfill receipt in notebook output: 26,086,569 blocks at 200 state partitions in 301.9 seconds of batch time" title="Backfill receipt in notebook output: 26,086,569 blocks at 200 state partitions in 301.9 seconds of batch time" srcset="https://substackcdn.com/image/fetch/$s_!jx44!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png 424w, https://substackcdn.com/image/fetch/$s_!jx44!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png 848w, https://substackcdn.com/image/fetch/$s_!jx44!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png 1272w, https://substackcdn.com/image/fetch/$s_!jx44!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96368b63-11f6-4e07-9073-3bd07d62b333_1400x644.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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">Notebook output from task run <code>298394343088892</code>: the backfill receipt for the checkpoint the rest of this post continues from.</figcaption></figure></div><p>The query processed 26,086,569  Ethereum blocks  from 4,080 files in 13 micro-batches, leaving 3,154 keys in state. The checkpoint now retained 200 state partitions.. That number is now baked into the checkpoint. We come back to it in Step 5.</p><h3><strong>Step 3: add the live feed, and watch the archive replay nothing</strong></h3><p>This is the cutover that used to cost a fresh checkpoint. The live feed is a Python data source over a public Ethereum RPC endpoint, configured to start one block after the archive&#8217;s last. It runs on a single small node, on the same checkpoint, with the archive source still present.</p><p>The important thing is what does <em>not</em> change. Both sources project to the same seven columns, so the union, the watermark, the aggregation and the sink are exactly the code that ran in Step 2. The only edit is one more entry in the list of sources:</p><pre><code><code>archive = (spark.readStream.format("parquet").schema(BLOCK_SCHEMA)
           .option("maxFilesPerTrigger", 365)
           .name("s3_backfill")                         # unchanged
           .load(landing))

live = (spark.readStream.format("eth_rpc")             # Python DataSource
        .option("start_block", archive_max_block + 1)  # one past the archive
        .name("eth_live")                              # NEW named source
        .load())

# Same projection for every source, so the union is by name.
events = (archive.select(*BLOCK_COLUMNS).withColumn("source", lit("s3_backfill"))
          .unionByName(
          live.select(*BLOCK_COLUMNS).withColumn("source", lit("eth_live"))))

# The metric. Unchanged from Step 2.
counts = (events
          .withWatermark("block_ts", "2 days")
          .groupBy(window("block_ts", "5 minutes"), "source", "miner")
          .count())

(counts.writeStream
       .outputMode("update")
       .option("checkpointLocation", checkpoint)        # SAME checkpoint as Step 2
       .foreachBatch(upsert_into_delta)                 # MERGE on (window, source, miner)
       .start())</code></code></pre><p>Spark reads the offset log by name, finds <code>s3_backfill</code> at its last committed offset, finds no entry for <code>eth_live</code>, and starts the new source from its beginning. The RocksDB state for every open window carries straight over, because the stateful operator did not change.</p><p>To see what happened on restart, we flattened the query&#8217;s own <code>StreamingQueryProgress</code> events (<code>query.recentProgress</code>, or a <code>StreamingQueryListener</code>) into one row per source per microbatch. Each event carries a <code>sources[]</code> array with a <code>SourceProgress</code> per input: its <code>description</code>, <code>numInputRows</code>, <code>startOffset</code> and <code>endOffset</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UPAy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UPAy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png 424w, https://substackcdn.com/image/fetch/$s_!UPAy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png 848w, https://substackcdn.com/image/fetch/$s_!UPAy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png 1272w, https://substackcdn.com/image/fetch/$s_!UPAy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!UPAy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png" width="1400" height="489" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:489,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Notebook output listing both sources per microbatch; the FileStreamSource rows read 0 input rows and keep the same offset&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="Notebook output listing both sources per microbatch; the FileStreamSource rows read 0 input rows and keep the same offset" title="Notebook output listing both sources per microbatch; the FileStreamSource rows read 0 input rows and keep the same offset" srcset="https://substackcdn.com/image/fetch/$s_!UPAy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png 424w, https://substackcdn.com/image/fetch/$s_!UPAy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png 848w, https://substackcdn.com/image/fetch/$s_!UPAy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png 1272w, https://substackcdn.com/image/fetch/$s_!UPAy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fdf0bea-91fb-4445-ad42-5821d185e9c2_1400x489.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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">Task run <code>699608591493844</code>: one row per source per batch, flattened from <code>StreamingQueryProgress.sources[]</code>. Batch ids continue from 13, where the backfill stopped.</figcaption></figure></div><p>Three things to read off this table:</p><ul><li><p><strong>The batch ids start at 13, not 0.</strong> Step 2 committed batches 0 through 12. A fresh checkpoint would have restarted the count; this one continued it.</p></li><li><p><strong>The </strong><code>FileStreamSource</code><strong> rows (</strong><code>srcIdx 0</code><strong>) read 0 input rows in every batch,</strong> with <code>startOffset</code> and <code>endOffset</code> both held at <code>{"logOffset": 11}</code>. The archive resumed exactly where it stopped and re-read nothing.</p></li><li><p><strong>The new source (</strong><code>srcIdx 1</code><strong>) starts from </strong><code>null</code> in batch 13, then walks forward 20 blocks per batch from 26,086,590. It had no committed offset, so it began at its configured start block.</p></li></ul><p><strong>The new source started from its beginning; the old one resumed exactly where it stopped, and no history was re-read.</strong> Nothing about the aggregation state was touched.</p><blockquote><p><strong>Why is the </strong><code>name</code><strong> column empty when every source has a </strong><code>.name()</code><strong>?</strong> </p><p><code>.name()</code> is a checkpoint identity, not a runtime label: on Databricks Runtime 18.3, <code>SourceProgress</code> describes sources by class and description. Names live in the checkpoint&#8217;s <code>offsets/</code> directory, in V2 entries keyed by source name (Step 4 reads one); verify by <code>srcIdx</code>, offsets, and row counts.</p></blockquote><h3><strong>Step 4: Retire the source or keep it running, depends on your use-case</strong></h3><p>Once the live feed has been running for a while, the archive has nothing left to contribute. Retiring it is not a migration; it is a restart with a shorter list. Stop the query, edit the same pipeline code and delete <code>s3_backfill</code> code, and start it against the same checkpoint. Nothing else is edited:</p><pre><code><code>query.stop()

live = (spark.readStream.format("eth_rpc")
        .option("start_block", archive_max_block + 1)  # ignored: offset comes from the checkpoint
        .name("eth_live")                              # unchanged
        .load())

# No union any more. Same projection, same metric, same sink.
events = live.select(*BLOCK_COLUMNS).withColumn("source", lit("eth_live"))

counts = (events
          .withWatermark("block_ts", "2 days")
          .groupBy(window("block_ts", "5 minutes"), "source", "miner")
          .count())

(counts.writeStream
       .outputMode("update")
       .option("checkpointLocation", checkpoint)        # SAME checkpoint
       .foreachBatch(upsert_into_delta)
       .start())</code></code></pre><p>On restart Spark compares the named sources in the plan with the named sources in the newest offset-log entry. <code>eth_live</code> is in both, so it resumes from its committed offset (the <code>start_block</code> option is only honoured the first time a name is seen). <code>s3_backfill</code> is in the log but not in the plan, so Spark records its departure. The offset log keeps the name, marked with a tombstone, and the live source carries on:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ebGC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ebGC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png 424w, https://substackcdn.com/image/fetch/$s_!ebGC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png 848w, https://substackcdn.com/image/fetch/$s_!ebGC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png 1272w, https://substackcdn.com/image/fetch/$s_!ebGC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!ebGC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png" width="1400" height="972" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:972,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The raw V2 offset log entry after the removal, with s3_backfill marked as a tombstone and eth_live at its next block, above a readback: 3,060 archive keys in RocksDB against 3,060 open archive windows in Delta&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="The raw V2 offset log entry after the removal, with s3_backfill marked as a tombstone and eth_live at its next block, above a readback: 3,060 archive keys in RocksDB against 3,060 open archive windows in Delta" title="The raw V2 offset log entry after the removal, with s3_backfill marked as a tombstone and eth_live at its next block, above a readback: 3,060 archive keys in RocksDB against 3,060 open archive windows in Delta" srcset="https://substackcdn.com/image/fetch/$s_!ebGC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png 424w, https://substackcdn.com/image/fetch/$s_!ebGC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png 848w, https://substackcdn.com/image/fetch/$s_!ebGC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png 1272w, https://substackcdn.com/image/fetch/$s_!ebGC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82469ca3-d679-4c26-a3b1-65612e427480_1400x972.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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">Notebook output from task run <code>699608591493844</code>. Top: the newest offset-log entry after the removal. Bottom: the state store read back against Delta.</figcaption></figure></div><p>Two behaviours are worth noticing. First, <strong>removing a source does not remove its state.</strong> At removal, RocksDB still held 3,060 archive keys, exactly one per archive row in Delta whose window the watermark had not yet closed. Those windows will close on their own schedule, as they should. Second, <strong>the name is gone for good.</strong> Re-attach <code>s3_backfill</code> and the query refuses to start:</p><pre><code><code>[STREAMING_QUERY_EVOLUTION_ERROR.TOMBSTONE_SOURCE_NAME_REUSE] Cannot reuse tombstoned source names: s3_backfill. These source names were previously used and then removed from the streaming query at checkpoint location ... Reusing tombstoned source names can lead to data correctness issues. Please use different source names.</code></code></pre><p>You want that error. A reusable name would let a source silently rewind to an offset from two restarts ago and double-count every window it re-read.</p><h3><strong>Step 5: shrink the state to fit the live workload</strong></h3><p>With the archive gone, the query is processing 20 blocks per batch on one node, but its state is still spread across the backfill&#8217;s 200 partitions. Changing <code>spark.sql.shuffle.partitions</code> in the session does nothing; the checkpoint remembers 200.</p><pre><code><code># Resizes state on the next restart. shuffle.partitions cannot.
spark.conf.set("spark.sql.streaming.stateStore.partitions", "8")</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4prZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4prZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png 424w, https://substackcdn.com/image/fetch/$s_!4prZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png 848w, https://substackcdn.com/image/fetch/$s_!4prZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png 1272w, https://substackcdn.com/image/fetch/$s_!4prZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!4prZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png" width="1400" height="560" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:560,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Per-batch addBatch time and median state memory for the live feed at 200 partitions against 8 partitions&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="Per-batch addBatch time and median state memory for the live feed at 200 partitions against 8 partitions" title="Per-batch addBatch time and median state memory for the live feed at 200 partitions against 8 partitions" srcset="https://substackcdn.com/image/fetch/$s_!4prZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png 424w, https://substackcdn.com/image/fetch/$s_!4prZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png 848w, https://substackcdn.com/image/fetch/$s_!4prZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png 1272w, https://substackcdn.com/image/fetch/$s_!4prZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78f80fc8-68ac-4db8-a67b-5d2555f5c87a_1400x560.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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">Drawn from the run&#8217;s own <code>StreamingQueryProgress</code> records, unedited. Every point is a batch of exactly 20 blocks.</figcaption></figure></div><p>The restart&#8217;s first progress event reports 66.8 seconds of <code>controlBatch.REPARTITION</code>, with no keys lost: 3,155 before, 3,157 after, 0 removed. From then on the same 20-block batches took a median 15.0 seconds of <code>addBatch</code> instead of 35.5, and state memory fell from 1,323 MB to 1,067 MB. Same checkpoint, same query id, a third of the partitions&#8217; overhead gone.</p><h2><strong>Proof: two clusters, four phases, one query id</strong></h2><p>Spark derives a streaming query&#8217;s id from checkpoint metadata, so an unchanged id across restarts is direct evidence that all four phases were the same logical query resuming, not four queries that happened to write to the same table. Both clusters&#8217; Spark UIs show it:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gaG3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gaG3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png 424w, https://substackcdn.com/image/fetch/$s_!gaG3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png 848w, https://substackcdn.com/image/fetch/$s_!gaG3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png 1272w, https://substackcdn.com/image/fetch/$s_!gaG3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!gaG3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png" width="1400" height="774" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:774,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Structured Streaming tabs from the wide cluster and the single node, listing four finished runs that share one query Id&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="Structured Streaming tabs from the wide cluster and the single node, listing four finished runs that share one query Id" title="Structured Streaming tabs from the wide cluster and the single node, listing four finished runs that share one query Id" srcset="https://substackcdn.com/image/fetch/$s_!gaG3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png 424w, https://substackcdn.com/image/fetch/$s_!gaG3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png 848w, https://substackcdn.com/image/fetch/$s_!gaG3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png 1272w, https://substackcdn.com/image/fetch/$s_!gaG3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70669d7d-72dc-49ac-bb72-d4d3a299e147_1400x774.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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">Captured from each live driver while the job ran. Top: the eight-worker backfill cluster. Bottom: the single node that ran everything after.</figcaption></figure></div><p>Nothing fell through the seams. Delta holds 26,087,209 blocks: the archive&#8217;s 26,086,569 plus 640 live blocks starting at 26,086,570, exactly one past the archive&#8217;s last.</p><h2><strong>What this means for how you design streams</strong></h2><p>The demo is a blockchain, but the principal is universal: a long-lived metric fed by sources which will come and go. Once the job no longer has to be rebuilt when its inputs change, several patterns become routine restarts instead of migration projects:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KxFq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KxFq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png 424w, https://substackcdn.com/image/fetch/$s_!KxFq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png 848w, https://substackcdn.com/image/fetch/$s_!KxFq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png 1272w, https://substackcdn.com/image/fetch/$s_!KxFq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!KxFq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png" width="1456" height="920" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:920,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:259920,&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://www.databricksters.com/i/218210934?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.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_!KxFq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png 424w, https://substackcdn.com/image/fetch/$s_!KxFq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png 848w, https://substackcdn.com/image/fetch/$s_!KxFq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png 1272w, https://substackcdn.com/image/fetch/$s_!KxFq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3f4486-b1ee-42ff-83e8-e1f42b3f75dc_1636x1034.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>And four rules to carry into every new stream:</p><ol><li><p><strong>Assume every source is temporary, and name it on day one.</strong> Naming needs a fresh checkpoint, so the cheapest moment is before the first batch, and the sources you are most certain about are the ones you will be most surprised by.</p></li><li><p><strong>Treat names as permanent identifiers.</strong> A removed name is tombstoned; a rename is a remove plus an add that starts from the beginning.</p></li><li><p><strong>Removing a source keeps its state</strong> until the watermark closes it. Plan for that in memory sizing.</p></li><li><p><strong>Remember to alter </strong><code>spark.sql.streaming.stateStore.partitions</code><strong>: </strong>when you&#8217;re backfilling set this number high so you can backfill fast, and then remember to bring down that number  when you go in live state.</p></li></ol><h2>Common misconceptions about streaming source lifecycle management</h2><p>The lifecycle test exposes several common misconceptions; confusing identity, offsets, and retained state causes replay, double counting, or failed restarts.</p><ul><li><p><strong>Misconception: a source list is fixed for the life of its checkpoint. Correction:</strong> with source evolution from a fresh checkpoint and stable names, sources can be added, removed, or reordered across restarts without discarding state.</p></li><li><p><strong>Misconception: adding a source causes existing sources to replay. Correction:</strong> existing named sources resume from their committed offsets; only the newly introduced name starts from its configured beginning.</p></li><li><p><strong>Misconception: removing a source immediately deletes its state. Correction:</strong> removal tombstones the name; existing state remains until normal watermark eviction closes it.</p></li><li><p><strong>Misconception: a removed source name can later be reused. Correction:</strong> tombstoned names cannot be reused, because doing so could rewind offsets and double-count data.</p></li><li><p><strong>Misconception: source names appear as labels in </strong><code>StreamingQueryProgress</code><strong>. Correction:</strong> on the tested runtime, <code>.name()</code> is a checkpoint identity; verify by source index, offsets, row counts, or V2 offset-log entries.</p></li><li><p><strong>Misconception: changing </strong><code>spark.sql.shuffle.partitions</code><strong> resizes an existing state store. Correction:</strong> the checkpoint keeps its count; use <code>spark.sql.streaming.stateStore.partitions</code> with on-demand repartitioning.</p></li></ul><h2><strong>Scope of this run</strong></h2><ul><li><p><strong>Runtime and compute.</strong> Databricks Runtime 18.3 on classic compute. Source evolution requires Databricks Runtime 18.2 or above; on-demand state repartitioning is in Public Preview and requires the RocksDB state store.</p></li><li><p><strong>Structured Streaming jobs.</strong> This run used plain Structured Streaming with <code>foreachBatch</code>. It did not exercise Lakeflow Spark Declarative Pipelines or real-time mode.</p></li><li><p><strong>Not a benchmark.</strong> One workload, one run. The partition-count timings are illustrative of the mechanism, not a performance claim. Things can be done in milli-seconds end to end as well.</p></li></ul><h2><strong>Get started</strong></h2><p>Two configs and a <code>.name()</code> on each source is all it takes to decouple the job from its inputs:</p><pre><code><code>spark.conf.set("spark.sql.streaming.queryEvolution.enableSourceEvolution", "true")
spark.conf.set("spark.sql.streaming.offsetLog.formatVersion", "2")

orders_us = spark.readStream.name("orders_us").table("catalog.schema.orders_us")
orders_eu = spark.readStream.name("orders_eu").table("catalog.schema.orders_eu")
all_orders = orders_us.union(orders_eu)</code></code></pre><ul><li><p><a href="https://docs.databricks.com/aws/en/structured-streaming/checkpoints#change-streaming-sources-with-source-evolution">Change streaming sources with source evolution</a></p></li><li><p><a href="https://docs.databricks.com/aws/en/structured-streaming/state-repartitioning">On-demand state repartitioning for stateful streaming queries</a></p></li><li><p><a href="https://docs.databricks.com/aws/en/structured-streaming/triggers">Configure Structured Streaming trigger intervals</a></p></li><li><p><a href="https://registry.opendata.aws/aws-public-blockchain/">AWS Public Blockchain Data</a></p></li></ul><h2>FAQ</h2><p><strong>Q: What is source evolution in Structured Streaming?</strong><br><strong>A:</strong> Source evolution lets a new checkpoint track each source by a stable <code>.name()</code> rather than query-plan position. With offset-log format V2 and an otherwise compatible plan, named sources can be added, removed, or reordered across restarts.</p><p><strong>Q: Do I need a new checkpoint to enable source evolution?</strong><br><strong>A:</strong> Yes. Source evolution cannot be enabled retroactively, and it cannot be disabled after the checkpoint is created. It requires Databricks Runtime 18.2 or later.</p><p><strong>Q: Does adding a source replay existing sources?</strong><br><strong>A:</strong> No. Existing named sources resume from their committed offsets. Only the new source name starts from its configured beginning.</p><p><strong>Q: What happens to state when a source is removed?</strong><br><strong>A:</strong> The source name is tombstoned, but its existing aggregation state remains until normal watermark eviction closes the associated windows.</p><p><strong>Q: Can I change the number of state partitions without losing state?</strong><br><strong>A:</strong> With supported on-demand state repartitioning and RocksDB, set <code>spark.sql.streaming.stateStore.partitions</code> and restart. Changing <code>spark.sql.shuffle.partitions</code> does not resize state already recorded in the checkpoint.</p><p><strong>Q: Can a tombstoned source name be reused?</strong><br><strong>A:</strong> No. Spark blocks reuse because an old committed offset could rewind the feed and double-count data. A returning feed requires a different name and therefore starts from that new name&#8217;s configured beginning.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><item><title><![CDATA[Best practices for GPU Model Training on Databricks AI Runtime- Part 2]]></title><description><![CDATA[TL;DR Use asynchronous checkpointing to UC Volumes with UCVolumeWriter and checkpoint every 30 to 60 minutes to balance resilience against I/O overhead.]]></description><link>https://www.databricksters.com/p/best-practices-for-gpu-model-training-1ad</link><guid isPermaLink="false">https://www.databricksters.com/p/best-practices-for-gpu-model-training-1ad</guid><dc:creator><![CDATA[Gautam Srinivasan]]></dc:creator><pubDate>Wed, 23 Sep 2026 15:12:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/aae412ba-e968-4aa3-8705-78c0db5a7804_1456x694.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong><span>TL;DR</span></strong></p><ul><li><p><span>Use asynchronous checkpointing to UC Volumes with </span><code>UCVolumeWriter</code><span> and checkpoint every 30 to 60 minutes to balance resilience against I/O overhead.</span></p></li><li><p><span>Connect your local IDE (VS Code, Cursor) or terminal directly to AI Runtime GPUs via SSH tunnel for interactive development and debugging.</span></p></li><li><p><span>Use Genie Code for AI Runtime work. It natively understands your cluster, environment, and AI Runtime APIs, and more than doubled the success rate of leading coding agents on real data science tasks.</span></p></li><li><p><span>Productionize training with Declarative Automation Bundles (DABs) for version-controlled, CI/CD-friendly GPU workload deployments.</span></p></li><li><p><span>Run Ray Core, Ray Data, Ray Train, and Ray Tune natively on AI Runtime serverless GPU compute.</span></p></li><li><p><span>Monitor and attribute GPU spend using </span><code>system.billing.usage</code><span> with Serverless Usage Policies for team-level cost tracking.</span></p></li><li><p><span>Migrate deep learning workloads from classic DBR ML to AI Runtime for on-demand GPUs, zero idle cost, and a simpler infrastructure experience.</span></p></li></ul><p><span>This is a continuation of our blog that covers practical lessons we&#8217;ve learned from working with different customers shipping real GPU workloads on AI Runtime. Think of it as the &#8220;things we wish someone told us on day one&#8221; guide.</span></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><span>In Part 1, we covered hardware options, environments, entry points (notebooks, CLI, IDE), MLflow observability, and data loading.</span></p><p><span>In Part 2, we cover checkpointing, remote development workflows, Genie Code for AI Runtime, productionizing training runs, Ray on AI Runtime, migrating from classic DBR ML, and cost governance.</span></p><p><span>For the full Part 1, see </span><a href="https://www.databricksters.com/p/best-practices-for-gpu-model-training"><span>Best Practices for GPU Workloads on Databricks AI Runtime &#8212; Part 1</span></a></p><h3><strong><span>6. Checkpointing: Make AI workloads resilient and resumable</span></strong></h3><p><strong><span>Model Checkpointing</span></strong></p><p><span>Use asynchronous checkpointing to UC volumes with UCVolumeWriter and PyTorch DCP to avoid blocking training. These backends stage data on fast local NVMe (/tmp) before uploading, which is faster than writing directly to FUSE and ensures metadata is only committed after all shards are uploaded.</span></p><p><span>Checkpoint frequently enough to limit lost work, but not so often that I/O slows training ,typically every 30&#8211;60 minutes. For async saves, initialize the process group with a CPU backend (e.g., cpu:gloo, cuda:nccl).</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;6ec69814-8a0e-4d48-9739-b8f1abda1d0d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import torch.distributed.checkpoint as dcp

from serverless_gpu.data import UCVolumeWriter

checkpoint_path = &#8220;/Volumes/my_catalog/my_schema/model/checkpoints&#8221;

writer = UCVolumeWriter(checkpoint_path)

future = dcp.async_save(state_dict, storage_writer=writer)

# ...continue training...

future.result()  # blocks until the upload lands on the UC volume

# Load a checkpoint with UCVolumeReader

from serverless_gpu.data import UCVolumeReader

reader = UCVolumeReader(checkpoint_path)

dcp.load(state_dict, storage_reader=reader)</code></pre></div><p><strong><span>Data pipeline checkpointing</span></strong></p><p><span>A model checkpoint captures model and optimizer state, but not the position of your data pipeline within the dataset, so a resumed run cannot fast-forward to the exact sample where it stopped. Account for this in how you resume: restart from an epoch boundary, or track processed samples or shards in your own training state so you can skip them on resumption.</span></p><h3><strong><span>7. Remote Development for AI Runtime: A local-to-Databricks workflow</span></strong></h3><p><span>You can connect your local IDE or terminal directly to AI Runtime serverless GPU compute using an SSH tunnel. This lets you interactively run and debug code from VS Code, Cursor, or the CLI while keeping a consistent environment between your local setup and the Databricks workspace.</span></p><p><span>Tools like Cursor and VS Code (with the Remote SSH extension) work out of the box. Other coding agents like Claude Code can also be used once the SSH tunnel connection is active.</span></p><p><span>Setup is straightforward. Establish the connection via a single command in the Databricks CLI:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;shell&quot;,&quot;nodeId&quot;:&quot;1c3a41d3-8203-4a1c-80a7-2b7d28287a7e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-shell">databricks ssh connect --accelerator &lt;GPU_type&gt;</code></pre></div><p><span> where  </span><code>&lt;GPU_type&gt; </code><span>can be set to GPU_1xA10 or GPU_8xH100</span></p><p><span>To launch directly into your IDE, add the </span><code>--ide vscode</code><span> or </span><code>--ide cursor </code><span>parameter. You can also launch the tunnel through the latest release of the IDE extension.</span></p><p><span>Note that the SSH tunnel gives you an interactive session on a single node. For long-running training jobs or multi-node distributed training, use the AIR CLI instead (covered in Part 1).</span></p><p><span>Additional capabilities to simplify using your preferred IDE as your primary development environment:</span></p><ul><li><p><span>Handling project dependencies: Use the --base-environment option to start the SSH connection with pre-configured Python libraries from a workspace base environment.</span></p></li><li><p><span>Tracking costs and resource utilization: Apply a serverless usage policy using the </span><code>--usage-policy-id</code><span> parameter to attribute SSH connection costs across teams or projects.</span></p></li><li><p><span>Exploring data assets via Unity Catalog: Inspect catalogs, schemas, and resources directly within the IDE without leaving your development workflow.</span></p></li><li><p><span>File persistence: Files under </span><code>/Workspace, /Volumes</code><span>, and </span><code>/dbfs</code><span> persist across restarts. Anything in </span><code>/home</code><span> or </span><code>/root</code><span> is ephemeral.</span></p></li></ul><h3><strong><span>8. Genie Code: Build AI Runtime notebooks and jobs faster</span></strong></h3><p><strong><span><br></span></strong><a href="https://docs.databricks.com/aws/en/genie-code/"><span>Genie Code</span></a><span> is the AI coding and data assistant built into the Databricks workspace. It has a deep, native understanding of the Databricks platform including your data assets, lineage, governance policies, compute environment, and runtime context. It&#8217;s equipped with the latest Databricks-specific skills and APIs that general-purpose coding agents don&#8217;t have access to, and its agentic harness is purpose-built for the full spectrum of Databricks workloads, from data engineering and analytics to ML training and production operations.</span></p><p><span>The result: on real-world data science tasks, Genie Code more than doubled the success rate of a leading coding agent, even when that agent was connected to Databricks via MCP.</span></p><p><strong><span>When to Use Genie Code vs. Other Assistants<br></span></strong><span>If the work touches Databricks, start with Genie Code. For everything else, use whatever you&#8217;re already productive with, whether that&#8217;s Claude Code, Cursor, or Copilot. They&#8217;re great for general software engineering, building apps, and working across repos. These tools aren&#8217;t competitors, they&#8217;re complementary.</span></p><p><span>If Genie Code can&#8217;t be used, connect your preferred coding agent to Databricks using MCP and the open-source </span><strong><a href="https://github.com/databricks/databricks-agent-skills"><span>Databricks AI Tools</span></a></strong><span>. It won&#8217;t match Genie Code&#8217;s native depth, especially on AI Runtime, but it brings meaningful Databricks awareness to tools like Claude Code, Cursor, or Copilot.</span></p><p><strong><span>Leveraging Genie Code for AI Runtime Development<br></span></strong><span>Genie Code can help you develop and troubleshoot deep learning workloads in notebooks connected to AI Runtime. It can generate distributed training code, resolve environment and dependency issues, and investigate GPU workload failures.</span></p><p><span>Steps to use Genie Code with AI Runtime:</span></p><ol><li><p><a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/connecting"><span>Connect the notebook to AI Runtime</span></a><span> and open the Genie Code pane (Genie lamp on top right)</span></p></li><li><p><span>Describe the model training workload that you want to develop or the problem that you want to resolve.</span></p></li></ol><p><span>Here are some example Genie code prompts for the following use cases to get you started</span></p><ul><li><p><span>Develop distributed training workloads:</span></p><ul><li><p><span>&#8220;</span><em><span>Adapt this training workload for AI Runtime and explain the important changes</span></em><span>.&#8221;</span></p></li></ul></li></ul><ul><li><p><span>Resolve environment and dependency issues:</span></p><ul><li><p><span>&#8220;</span><em><span>Diagnose this package compatibility error and recommend the smallest appropriate change.</span></em><span>&#8221;</span></p></li></ul></li></ul><ul><li><p><span>Debug GPU and distributed workloads:</span></p><ul><li><p><span>&#8220;</span><em><span>Analyze this failed distributed training run using the available output from every rank and identify the root cause</span></em><span>.&#8221;</span></p></li></ul></li></ul><p><span>For more details refer to the </span><a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/genie-code"><span>Use Genie Code with AI Runtime</span></a><span> documentation.</span></p><h3><strong><span>9. Productionize: take your training to production</span></strong></h3><p><span>Once your training code is working in a notebook, the next step is making it reproducible, scheduled, and deployable across environments. Databricks supports this through Declarative Automation Bundles (DABs), which let you define your AI Runtime training workload as code. In Part 1, we covered the AIR CLI for submitting training jobs. DABs are the next step when you need version-controlled, CI/CD-friendly deployments.</span></p><p><span>The </span><code>ai_runtime_task</code><span> is a job task type purpose-built for this. You point it at your training code, specify the GPU type and count, and declare your dependencies, all in a YAML bundle definition that lives in source control. From there, you can deploy to dev or production with a single CLI command, schedule it on a cadence, and compose it with other tasks like data preparation steps that run on CPU.</span></p><p><span>Key things to know:</span></p><ul><li><p><span>Your training code can be packaged as a tgz artifact and shipped with the bundle, or referenced directly from a workspace or volume path.</span></p></li><li><p><span>Multi-node runs are supported. Set accelerator_count higher than the per-node GPU count and AI Runtime handles the distributed environment variables (WORLD_SIZE, MASTER_ADDR, etc.) for you.</span></p></li><li><p><span>GPU and CPU tasks can be combined in multi-task jobs using </span><code>depends_on</code><span>, so you only use GPU time for actual training. Note that </span><code>ai_runtime_task</code><span> does not support job task values for passing data between steps, so use a shared location like a UC volume instead.</span></p></li><li><p><span>Dev to production promotion works the same way as any other Databricks bundle. Schedules are paused by default, so deploying won&#8217;t accidentally trigger runs.</span></p></li></ul><p><span>For the full configuration reference, task fields, and a complete working example, see the </span><a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/productionizing-training-workloads"><span>Productionize training workloads documentation</span></a><span>.</span></p><h3><strong><span>10. Ray on AI Runtime: Practical scaling with Ray Core, Ray Data, Ray Train and Ray Tune</span></strong></h3><p><span>Ray is a popular framework for scaling Python workloads. Databricks AI Runtime supports Ray natively, so you can create Ray clusters and run Ray applications on serverless GPU compute without managing infrastructure.</span></p><p><span>AI Runtime supports Ray&#8217;s main libraries, including Ray Core, Ray Data, Ray Train, and Ray Tune. The Databricks </span><code>AI</code><span> environment includes Ray out of the box. With the </span><code>Standard</code><span> environment, add </span><code>ray</code><span>  or the relevant extra (</span><code>ray[data], ray[train], ray[tune]</code><span>) to your workload dependencies.</span></p><p><strong><span>Use Ray in notebooks</span></strong></p><p><span>For single node, use </span><code>ray_init()</code><span> from the </span><code>serverless_gpu</code><span> package to start Ray on the notebook attached to AI Runtime compute:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;54464079-0803-4f7b-95df-9257ef426fea&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from serverless_gpu import ray_init
ray_init()</code></pre></div><p><span>Ray Dashboard: </span><code>ray_init() </code><span>automatically configures the Ray dashboard and prints the dashboard URL in the notebook output. Use the dashboard to inspect Ray jobs, tasks, and resource usage while your code runs. This is in addition to the MLflow System Metrics and Model Metrics monitoring you get for AI Runtime runs.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!H3LM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!H3LM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png 424w, https://substackcdn.com/image/fetch/$s_!H3LM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png 848w, https://substackcdn.com/image/fetch/$s_!H3LM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png 1272w, https://substackcdn.com/image/fetch/$s_!H3LM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!H3LM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png" width="1456" height="749" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:749,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!H3LM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png 424w, https://substackcdn.com/image/fetch/$s_!H3LM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png 848w, https://substackcdn.com/image/fetch/$s_!H3LM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png 1272w, https://substackcdn.com/image/fetch/$s_!H3LM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ecd157-5c3f-4782-8c8b-f76cbf393221_2048x1053.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><strong><span>Use Ray with the AI Runtime CLI</span></strong></p><p><span>The AI Runtime CLI supports Ray in single-node and multi-node configurations. Include a bootstrap script under the workload&#8217;s command, which starts the Ray cluster and coordinates head and worker nodes when the workload launches.</span></p><p><span>For Ray examples with CLI, refer to the </span><a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/ray#cli-examples"><span>AI Runtime Ray documentation</span></a><span>.</span></p><h3><strong><span>11. Migrate GPU workloads from Classic DBR ML to Databricks AI Runtime</span></strong></h3><p><span>Databricks AI Runtime is the recommended compute for GPU workloads.</span></p><p><span>If you&#8217;re still on classic DBR ML, here are the key steps to migrate deep learning workloads to AIR:</span></p><ul><li><p><strong><span>Replace cluster-dependent code</span></strong><span>. Remove any references to Spark-based distributed training (for example, TorchDistributor) and replace them with the </span><code>@distributed </code><span>decorator from </span><code>serverless_gpu</code><span>. Or you can go for the air cli approach.</span></p></li><li><p><strong><span>Reinstall dependencies</span></strong><span>. Do not rely on Databricks Runtime ML pre-installed libraries. Add explicit</span><code> %pip install </code>commands for all required packages with exact versions.</p></li><li><p><strong><span>Update data loading</span></strong><span>.</span></p><ul><li><p><span>Replace direct DBFS paths with Unity Catalog volumes paths.</span></p></li><li><p><span>Replace local Spark DataFrame operations with Spark Connect.</span></p></li><li><p><span>For streaming file-based data from volumes, use UCVolumeDataset from </span><code>serverless_gpu.data</code><span>.</span></p></li></ul></li></ul><ul><li><p><strong><span>Update checkpoint paths</span></strong><span>.</span></p><ul><li><p><span>Move checkpoints from DBFS or local storage to Unity Catalog volumes.</span></p></li><li><p><span>For distributed checkpointing, use </span><code>UCVolumeWriter</code><span> and </span><code>UCVolumeReader</code><span> from </span><code>serverless_gpu.data</code><span>, which stage I/O through local NVMe.</span></p></li></ul></li><li><p><strong><span>Update MLflow configuration</span></strong><span>. Ensure experiment names use absolute paths and configure run names so they can be easily restarted.</span></p></li><li><p><strong><span>Test interactively firs</span></strong><span>t. Validate your workload in an interactive notebook before scheduling it as a job.</span></p></li></ul><p><span>Several of these steps, such as environments, data loading, checkpointing, and MLflow configuration, are covered in detail earlier in the blog. Refer to those sections for deeper guidance.</span></p><p><span>Recommendation: Use Genie Code to accelerate the migration. It understands both the classic DBR ML patterns you&#8217;re moving from and the AI Runtime APIs you&#8217;re moving to, so it can help rewrite your training code, resolve dependency conflicts, and test the result in your live environment.</span></p><p><strong><span>12. Cost and Usage: Measure, govern, and optimize GPU compute</span></strong></p><p><span>AI Runtime will appear on your bill as DBUs on the Model Training SKU. The DBUs will be based on the number of GPU hours and the GPU type selected.</span></p><p><span>You can view on-demand pricing for the available GPU instance types in your cloud provider and region on the</span><a href="https://www.databricks.com/product/pricing/ai-runtime"><span> Databricks AI Runtime pricing page</span></a><span>.</span></p><p><span>You can monitor your AI Runtime GPU spend by querying the billable usage system table (system.billing.usage).</span></p><p><span>The following query returns total usage for serverless GPU workloads:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;f300b1b0-423a-498f-a386-2554cb62f7cb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">SELECT SUM(usage_quantity)

FROM system.billing.usage

WHERE product_features.serverless_gpu IS NOT NULL

</code></pre></div><p>Spend by team and model (via custom tags):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;6fe16903-0a0c-480a-9e57-d330caa35a6b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">SELECT custom_tags[&#8217;team&#8217;]  AS team,

       custom_tags[&#8217;model&#8217;] AS model,

       SUM(usage_quantity)  AS gpu_hours

FROM   system.billing.usage

WHERE  product_features.serverless_gpu IS NOT NULL

  AND  usage_date &gt;= current_date() - INTERVAL 7 DAYS

GROUP  BY team, model

ORDER  BY gpu_hours DESC;</code></pre></div><p><span>Tips</span></p><ul><li><p><span>Create Serverless Usage Policies with tags for team, project, or model so GPU spend is attributable from day one. These tags propagate automatically to </span><code>system.billing.usage</code><span> in the </span><code>custom_tags</code><span> column.</span></p></li><li><p><span>For notebooks, select the usage policy from the compute dropdown. For AIR CLI jobs, set </span><code>usage_policy_name</code><span> in your workload YAML. For DABs jobs, set it in the job details.</span></p></li><li><p><span>Build a Lakeview dashboard on top of these queries for weekly cost review. Databricks also offers a </span><a href="https://docs.databricks.com/aws/en/admin/account-settings/usage"><span>pre-built cost monitoring dashboard</span></a><span> that account admins can import.</span></p></li></ul><p><span>That covers it. Across Part 1 and Part 2, we&#8217;ve covered the full lifecycle of GPU workloads on AI Runtime. From choosing the right entry point, hardware, and environment, to data loading, distributed training, and observability in Part 1, through to checkpointing, developer experience, productionizing, and cost governance in Part 2. Everything here comes from design patterns we&#8217;ve seen work across real teams shipping real workloads. </span></p><p><span>We hope it gives you a practical understanding of how to make the best use of AI Runtime for your use cases. If you&#8217;re just getting started, pick the section most relevant to you and build from there. And as AI Runtime continues to evolve, we&#8217;ll keep updating these guides with what we learn.</span></p><div><hr></div><p>Thanks for reading. Feel free to share any feedback on topics you&#8217;d like to see next in the comments section.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><item><title><![CDATA[Turn Databricks Genie into Own Podcaster!]]></title><description><![CDATA[Databricks Genie is great at answering questions about your data.]]></description><link>https://www.databricksters.com/p/turn-databricks-genie-into-own-podcaster</link><guid isPermaLink="false">https://www.databricksters.com/p/turn-databricks-genie-into-own-podcaster</guid><dc:creator><![CDATA[Ambarish]]></dc:creator><pubDate>Mon, 31 Aug 2026 17:27:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5q-U!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5q-U!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5q-U!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!5q-U!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!5q-U!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!5q-U!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5q-U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_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;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!5q-U!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!5q-U!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!5q-U!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!5q-U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F917f6b55-4dcd-4ffa-8ba4-87adab8ba4da_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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>Databricks Genie is great at answering questions about your data. But you can&#8217;t listen to the details on your walk to standup, while driving to the office, or during your commute. So let&#8217;s build a Databricks App that asks Genie your questions and reads the answers back to you &#8212; as a short, spoken podcast you can play on your phone- anytime, anywhere!</span></p><h2><span>Features</span></h2><p><span>&#8226; &#128483;&#65039; Narrated answers &#8212; Each Genie summary is spoken aloud by an in-app voice model</span></p><p><span>&#8226; &#129504; No second LLM &#8212; Genie itself writes the spoken summary</span></p><p><span>&#8226; &#128202; Details on tap &#8212; Exact figures, generated SQL, Genie&#8217;s reasoning, and result rows stay on screen</span></p><p><span>&#8226; &#128272; Runs as you &#8212; Every Genie query runs on-behalf-of the signed-in user, with their permissions</span></p><p><span>&#8226; &#128190; Durable &#8212; Episodes live in Lakebase, audio in a UC Volume</span></p><p><span>&#8226; &#128241; Mobile-first &#8212; GenieOne App enables your Databricks App to be available on mobile devices</span></p><h2><span>Architecture</span></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gHsd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gHsd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png 424w, https://substackcdn.com/image/fetch/$s_!gHsd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png 848w, https://substackcdn.com/image/fetch/$s_!gHsd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png 1272w, https://substackcdn.com/image/fetch/$s_!gHsd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!gHsd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png" width="1452" height="823" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:823,&quot;width&quot;:1452,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!gHsd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png 424w, https://substackcdn.com/image/fetch/$s_!gHsd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png 848w, https://substackcdn.com/image/fetch/$s_!gHsd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png 1272w, https://substackcdn.com/image/fetch/$s_!gHsd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa2ff12d-d50a-47c9-bd5c-7188e76b7e8a_1452x823.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2><span>Prequisites</span></h2><ul><li><p><span>Databricks workspace with Databricks Apps enabled</span></p></li><li><p><span>Genie space you can query</span></p></li><li><p><span>Lakebase Instance</span></p></li><li><p><span>Unity Catalog Volume</span></p></li><li><p><span>Databricks App</span></p></li><li><p><span>Databricks CLI (v1.10+) with an auth profile</span></p></li></ul><h2><span>Setup Instructions</span></h2><p><span>Download the application code and make the necessary updates to the config values in app.yaml. Download all files from repo:</span></p><p><a href="https://github.com/adgitdemo/ad_databricks/tree/main/my-genie-podcaster"><span>https://github.com/adgitdemo/ad_databricks/tree/main/my-genie-podcaster</span></a></p><ol><li><p><span>Provision storage. Create a Lakebase database (note its branch + database identifiers). Create a UC Volume in an existing catalog/schema (note /Volumes/&lt;catalog&gt;/&lt;schema&gt;/&lt;volume&gt;).</span></p></li><li><p><span>Configure the bundle. In databricks.yml, set the workspace host, the postgres resource (branch, database, CAN_CONNECT_AND_CREATE), two uc_securable VOLUME resources (READ_VOLUME + WRITE_VOLUME), and user_api_scopes: [dashboards.genie]. In app.yaml, set LAKEBASE_ENDPOINT (valueFrom: postgres) and DATABRICKS_VOLUME_AUDIO.</span></p></li><li><p><span>Stage the model. npm install &amp;&amp; npm run setup:model to download the Kokoro weights into .hf-cache/&#8230;. Then databricks fs mkdir dbfs:/Volumes/&lt;&#8230;&gt;/_models/onnx-community/Kokoro-82M-v1.0-ONNX/onnx and databricks fs cp the four files (config.json, tokenizer.json, tokenizer_config.json, onnx/model_quantized.onnx) to the matching Volume paths. (On first boot the app copies them to local disk &#8212; no Hugging Face egress.)</span></p></li><li><p><span>Build, deploy, run. npm run build, then databricks bundle deploy --profile &lt;p&gt;, then databricks bundle run app --profile &lt;p&gt;. Open the printed app URL.</span></p></li></ol><p><span>Note: users need access to their Genie space and its data, or Genie returns a permission error.</span></p><h2><span>Usage</span></h2><ul><li><p><span>Tap New.</span></p><ul><li><p><span>Enter a Genie space (by name or ID) and one or more questions.</span></p></li><li><p><span>Hit Create podcast. The app asks Genie, captures each SUMMARY and DETAILS, and pre-synthesizes the narration in the background.</span></p></li></ul></li><li><p><span>Open the episode and press Play. Switch narrators from the voice picker. Expand &#8220;Detailed analysis&#8221; on any answer to see the figures, SQL, Genie&#8217;s reasoning, and a preview of the rows.</span></p></li></ul><h2><span>Demo</span></h2><p><span>Open the &#8220;My Genie Podcaster&#8221; app from the One mobile App and use it!</span></p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;338fef21-5e69-4b07-bdf7-4e974de3ce96&quot;,&quot;duration&quot;:null}"></div><p></p><p>This blog explores the art of the possible with Databricks Genie!</p>]]></content:encoded></item><item><title><![CDATA[How to Flatten OpenTelemetry Metrics in Spark 4x faster]]></title><description><![CDATA[Replace multiples explode_outer calls with a single generate operator]]></description><link>https://www.databricksters.com/p/how-to-flatten-opentelemetry-metrics</link><guid isPermaLink="false">https://www.databricksters.com/p/how-to-flatten-opentelemetry-metrics</guid><dc:creator><![CDATA[Canadian Data Guy]]></dc:creator><pubDate>Fri, 28 Aug 2026 15:01:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!q8c5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>TL;DR</h2><p>Nested JSON never arrives flat. Open telemetry metrics are the textbook case: one payload carries an array of resources, each resource carries scopes, each scope carries metrics, each metric carries data points. On Spark, you land the whole thing in a single VARIANT column and unroll it later. That unroll is what most raw-to-refined jobs actually spend their time doing.</p><p>The obvious way is one <code>explode_outer</code> per level. It works. It is also the part of the job that quietly costs you the most. The rest of this is the mechanical rewrite from N nested <code>explode_outer</code> calls into a single <code>explode_outer</code> of a <code>flatten</code>/<code>transform</code> tree, plus the one subtlety that makes the naive version silently wrong. General Spark technique for nested arrays in VARIANT or JSON. </p><p>The working example below uses a public OpenTelemetry <a href="https://github.com/open-telemetry/opentelemetry-proto/blob/main/examples/metrics.json">payload</a>.</p><h2>Why chaining explode_outer calls hurts cost and correctness</h2><p><strong>Cost.</strong> Each <code>explode_outer</code> compiles to a Generate operator. Four nested arrays mean four Generate hops, with parent columns &#8212; including the raw payload &#8212; carried between them.</p><p><strong>Correctness.</strong> <code>explode</code> drops rows for empty arrays; <code>explode_outer</code> preserves the parent and emits a null child for null or empty input. Any rewrite must reproduce that behavior exactly.</p><h4>Mapping the nested array structure of an OTLP payload</h4><pre><code><code>payload (VARIANT column `raw`)
&#9492;&#9472;&#9472; $.resourceMetrics[]                          rm
    &#9492;&#9472;&#9472; $.scopeMetrics[]                         sm
        &#9492;&#9472;&#9472; $.metrics[]                          metric
            &#9492;&#9472;&#9472; $.sum|gauge|summary|histogram|
                exponentialHistogram .dataPoints[]   dp   &lt;- leaf

Chain form:  raw --Generate--&gt; rm --Generate--&gt; sm --Generate--&gt; metric --Generate--&gt; dp
               (4 Generate hops, fat parent row carried through each)
Single form: raw --transform/flatten (no Generate)--&gt; array&lt;struct&lt;rm,sm,metric,dp&gt;&gt;
               --Generate--&gt; leaf
               (1 Generate hop, narrow leaf struct)
</code></code></pre><h4>Before: chaining four explode_outer calls</h4><p>Assume a table <code>otlp_metrics_raw(payload_id STRING, raw VARIANT)</code> with one payload per row:</p><pre><code><code>from pyspark.sql import functions as F
df = (
    spark.table("otlp_metrics_raw")
    .withColumn("rm", F.explode_outer(
        F.variant_get(F.col("raw"), "$.resourceMetrics", "array&lt;variant&gt;")))
    .withColumn("sm", F.explode_outer(
        F.variant_get(F.col("rm"), "$.scopeMetrics", "array&lt;variant&gt;")))
    .withColumn("metric", F.explode_outer(
        F.variant_get(F.col("sm"), "$.metrics", "array&lt;variant&gt;")))
    .withColumn("dp", F.explode_outer(F.coalesce(
        F.variant_get(F.col("metric"), "$.sum.dataPoints", "array&lt;variant&gt;"),
        F.variant_get(F.col("metric"), "$.gauge.dataPoints", "array&lt;variant&gt;"),
        F.variant_get(F.col("metric"), "$.summary.dataPoints", "array&lt;variant&gt;"),
        F.variant_get(F.col("metric"), "$.histogram.dataPoints", "array&lt;variant&gt;"),
        F.variant_get(F.col("metric"), "$.exponentialHistogram.dataPoints", "array&lt;variant&gt;"),
    )))
)</code></code></pre><h2>The rewrite rule: build the leaf struct from the inside out</h2><ol><li><p>At each parent level, <code>transform</code> the child array into an array of leaf structs, then <code>flatten</code> the result back to one level.</p></li><li><p>The leaf <code>transform</code> builds the struct &#8212; <code>struct(rm AS rm, sm AS sm, m AS metric, dp AS dp)</code> &#8212; carrying <strong>every ancestor handle</strong> the projection reads after the explode (need <code>$.schemaUrl</code> off the resource? Then <code>rm</code> must be in the struct).</p></li><li><p>The last step &#8212; and only the last step &#8212; is one <code>explode_outer</code> of that array.</p></li><li><p><strong>Restore outer semantics on every array</strong> &#8212; the step people skip, and the whole point of this post.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!q8c5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!q8c5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif 424w, https://substackcdn.com/image/fetch/$s_!q8c5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif 848w, https://substackcdn.com/image/fetch/$s_!q8c5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif 1272w, https://substackcdn.com/image/fetch/$s_!q8c5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!q8c5!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif" width="1200" height="1530.857142857143" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:1786,&quot;width&quot;:1400,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:672245,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.databricksters.com/i/212104867?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!q8c5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif 424w, https://substackcdn.com/image/fetch/$s_!q8c5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif 848w, https://substackcdn.com/image/fetch/$s_!q8c5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif 1272w, https://substackcdn.com/image/fetch/$s_!q8c5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bbc7a85-daea-410a-9cd2-8eeadd16255b_1400x1786.gif 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2>The hidden risk: flatten and transform lack outer semantics</h2><p>Swapping a chain of <code>explode_outer</code> calls for one <code>explode_outer</code> over a <code>flatten(transform(...))</code> tree trades an operator with <em>outer</em> behavior for two without it. <code>explode_outer</code> on a null or empty array keeps the parent row and emits one NULL-child row; <code>flatten</code> and <code>transform</code> make no such promise:</p><ul><li><p><strong>Empty inner array &#8594; the parent disappears.</strong> <code>transform</code> of an empty array yields an empty array, <code>flatten</code> drops it, and the parent never reaches the explode &#8212; <code>explode_outer</code> would have emitted one row.</p></li><li><p><strong>NULL inner array &#8594; the whole row collapses.</strong> <code>flatten</code> returns NULL if <em>any</em> element is NULL [4]. One resource missing <code>scopeMetrics</code> nulls the entire leaf array and takes every sibling leaf with it. The damage isn&#8217;t local to the bad element.</p></li></ul><pre><code><code>def null_to_singleton(array_expr: str) -&gt; str:
    """explode_outer parity: NULL/empty child array -&gt; one NULL placeholder."""
    return (
        f"CASE WHEN ({array_expr}) IS NULL OR size({array_expr}) = 0 "
        f"THEN array(CAST(NULL AS VARIANT)) ELSE ({array_expr}) END"
    )
</code></code></pre><p>Wrap every array in the chain &#8212; leaf included &#8212; and the rewrite is row-for-row identical to the chain it replaces.</p><h4>After: one explode_outer call replaces the chain</h4><pre><code><code>METRIC_KINDS = ("sum", "gauge", "summary", "histogram", "exponentialHistogram")

def null_to_singleton(array_expr: str) -&gt; str:
    return (
        f"CASE WHEN ({array_expr}) IS NULL OR size({array_expr}) = 0 "
        f"THEN array(CAST(NULL AS VARIANT)) ELSE ({array_expr}) END"
    )

def dp_array(metric: str) -&gt; str:
    kinds = ",\n            ".join(
        f"variant_get({metric}, '$.{k}.dataPoints', 'array&lt;variant&gt;')" for k in METRIC_KINDS
    )
    return f"coalesce(\n            {kinds}\n          )"

LEAF_ARRAY_EXPR = """explode_outer(
    flatten(transform({rm_arr}, rm -&gt;
      flatten(transform({sm_arr}, sm -&gt;
        flatten(transform({m_arr}, m -&gt;
          transform({dp_arr}, dp -&gt; struct(rm AS rm, sm AS sm, m AS metric, dp AS dp))
        ))
      ))
    ))
  )""".format(
    rm_arr=null_to_singleton("variant_get(raw, '$.resourceMetrics', 'array&lt;variant&gt;')"),
    sm_arr=null_to_singleton("variant_get(rm, '$.scopeMetrics', 'array&lt;variant&gt;')"),
    m_arr=null_to_singleton("variant_get(sm, '$.metrics', 'array&lt;variant&gt;')"),
    dp_arr=null_to_singleton(dp_array("m")),
)

FLATTEN_SQL = """
SELECT
  payload_id,
  rm,
  sm,
  metric,
  dp,
  variant_get(metric, '$.name', 'string')  AS metric_name,
  variant_get(metric, '$.unit', 'string')  AS metric_unit,
  CAST(variant_get(dp, '$.startTimeUnixNano', 'long') / 1e9 AS TIMESTAMP)
                                           AS start_timestamp,
  CAST(variant_get(dp, '$.timeUnixNano', 'long') / 1e9 AS TIMESTAMP)
                                           AS event_timestamp,
  variant_get(dp, '$.asInt', 'long')       AS value_as_int,
  variant_get(dp, '$.asDouble', 'double')  AS value_as_double
FROM (
  SELECT
    payload_id,
    leaf.rm     AS rm,
    leaf.sm     AS sm,
    leaf.metric AS metric,
    leaf.dp     AS dp
  FROM (
    SELECT payload_id, {leaf_array_expr} AS leaf
    FROM otlp_metrics_raw
  )
)
""".format(leaf_array_expr=LEAF_ARRAY_EXPR)

flat = spark.sql(FLATTEN_SQL)</code></code></pre><p><code>rm</code>, <code>sm</code>, <code>metric</code>, and <code>dp</code> remain VARIANT handles in the projection; resource attributes, scope name, bucket counts, exemplars &#8212; anything you need later is a <code>variant_get</code> away, without touching the unroll.</p><h2>General checklist for flattening any nested JSON array</h2><ol><li><p>List the arrays from root to leaf.</p></li><li><p>Define a leaf struct containing the leaf and every required ancestor.</p></li><li><p>Replace each null or empty array with a typed singleton-NULL array.</p></li><li><p>Build nested <code>transform</code> and <code>flatten</code> expressions from the inside out.</p></li><li><p>Call <code>explode_outer</code> exactly once, at the top.</p></li><li><p>Verify Generate counts and compare rows against the original chain.</p></li></ol><p><strong>Rule:</strong> N nested <code>explode_outer</code> calls become one <code>explode_outer</code> over a guarded <code>flatten(transform(...))</code> tree that pre-builds the leaf structs.</p><h2>FAQ</h2><p><strong>Q: What if my input data is not balanced, it is never balanced   ?</strong> </p><p><strong>A: </strong> Explicitly run a repartition command so the input gets balanced. </p><p><strong>Q: What if my input data is not balanced, it is never balanced   ?</strong> </p><p><strong>A: </strong> Set maxFilesPerTrigger or maxBytesPerTrigger, or similar  options to control the size of the MicroBatch </p><p><strong>Q: How to check spark behavior before executing ?</strong> </p><p><strong>A:</strong>  Run <code>explain()</code> on both forms and count the <code>Generate</code> nodes. Four becomes one &#8212; the entire structural claim, verifiable in one command:</p><pre><code><code>spark.sql(FLATTEN_SQL).explain()   # count `Generate` nodes: expect exactly 1
r.</code></code></pre><p><strong>Q: Does this technique require the VARIANT type?</strong> </p><p><strong>A:</strong> No. <code>transform</code>, <code>flatten</code>, and <code>explode_outer</code> are standard Spark SQL functions. For parsed structs, replace <code>variant_get</code> with field access and cast each NULL placeholder to the array&#8217;s element type.</p><p><strong>Q: Does the same rule apply to OTLP logs?</strong> </p><p><strong>A:</strong> Yes. OTLP logs use three array levels: <code>resourceLogs[]</code> &#8594; <code>scopeLogs[]</code> &#8594; <code>logRecords[]</code> [3]. Remove one <code>flatten(transform(...))</code> level and the corresponding leaf-struct field..</p><p><strong>Q: Is one explode_outer always faster than a chain?</strong> </p><p><strong>A:</strong> Almost always. It removes Generate operators but may widen the row before the remaining Generate and adds generated-SQL complexity. Benchmark both implementations on representative data.</p><p><strong>Q: What happens if one array is left unguarded?</strong> </p><p><strong>A:</strong> You can lose rows silently. An empty array removes its parent leaf, while a NULL inner array can null the entire flattened array and remove otherwise-valid sibling rows.</p><div><hr></div><h3><strong>References</strong></h3><p>[1] <a href="https://github.com/open-telemetry/opentelemetry-proto/blob/main/examples/metrics.json">opentelemetry-proto/examples/metrics.json</a> &#8212; Apache-2.0. Raw: <a href="https://raw.githubusercontent.com/open-telemetry/opentelemetry-proto/main/examples/metrics.json">https://raw.githubusercontent.com/open-telemetry/opentelemetry-proto/main/examples/metrics.json</a></p><p>[2] <a href="https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/metrics/v1/metrics.proto">opentelemetry/proto/metrics/v1/metrics.proto</a> </p><p>[3] <a href="https://github.com/open-telemetry/opentelemetry-proto/blob/main/examples/logs.json">opentelemetry-proto/examples/logs.json</a></p><h4><strong>Spark SQL references</strong></h4><p>[4] <a href="https://spark.apache.org/docs/latest/api/sql/index.html#flatten">Spark SQL, Built-in Functions &#8212; flatten</a> &#8212; &#8220;Returns NULL if any element of the input array is NULL.&#8221;</p><p>[5] <a href="https://spark.apache.org/docs/latest/api/sql/index.html#transform">Spark SQL, Built-in Functions &#8212; transform</a> &#8212; higher-order function; returns an empty array for empty input.</p><p>[6] <a href="https://spark.apache.org/docs/latest/api/sql/index.html#explode_outer">Spark SQL, Built-in Functions &#8212; explode_outer</a> &#8212; &#8220;Returns null for entries that are null or empty.&#8221;</p><p></p>]]></content:encoded></item><item><title><![CDATA[Making UC Metric Views Discoverable for Agents]]></title><description><![CDATA[Why does a semantic layer matter?]]></description><link>https://www.databricksters.com/p/making-uc-metric-views-discoverable</link><guid isPermaLink="false">https://www.databricksters.com/p/making-uc-metric-views-discoverable</guid><dc:creator><![CDATA[Chaman Jalan]]></dc:creator><pubDate>Wed, 29 Jul 2026 15:01:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fn3V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1><strong><span>Why does a semantic layer matter?</span></strong></h1><p style="text-align: justify;"><span>Every company depends on a handful of KPIs: revenue, active users, churn, margin, and each one usually gets defined in a dozen places at once: a SQL query here, a spreadsheet formula there, a dashboard filter somewhere else, each subtly different, until two teams walk into a meeting with two revenue figures and no one can say which is right. A semantic layer fixes that by providing a single governing definition that every tool reads, and </span><a href="https://docs.databricks.com/aws/en/business-semantics/metric-views"><span>Unity Catalog Metric Views</span></a><span> are Databricks&#8217; version of it.</span></p><p style="text-align: justify;"><span>That consistency has always mattered, but AI raises the stakes. When an agent answers a business question at machine speed, a wrong or ambiguous definition no longer just misleads one analyst; it gets baked into automated decisions and customer-facing answers. The semantic layer is where trust in your data is enforced, and your agents are only as good as the definitions behind them. This post is about the newest consumer of that layer, the AI agent, and a lightweight pattern that lets it reach the semantic layer using only existing Databricks services.</span></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><h2><strong><span>Agents can&#8217;t find the </span></strong><em><strong><span>right</span></strong></em><strong><span> metric definition</span></strong></h2><p style="text-align: justify;"><span>A person finds the right metric view without thinking about it. An agent starts from nothing. When a custom agent receives a question like </span><em><span>&#8220;what was our average order value this quarter?&#8221;</span></em><span>, it first has to find the right metric view from among hundreds across dozens of domains, and then understand its measures, dimensions, and query syntax.</span></p><p style="text-align: justify;"><span>Today, there&#8217;s no first-class, public way to search across them. An agent can already reach the definitions: it can enumerate catalog objects and read a metric view&#8217;s definition through the Unity Catalog REST API or SQL, and run one with the MEASURE() function. The trouble is choosing. Enumerating and parsing raw definitions on every call doesn&#8217;t scale past a small catalog. It re-scans each time, burns tokens, and surfaces overlapping or conflicting definitions with no way to rank them.</span></p><p style="text-align: justify;"><span>The semantic layer is a valuable, governed asset, but there&#8217;s no discovery surface that lets an agent ask, &#8220;Given this question, which metric view should I use, and what does it expose?&#8221; the way it would for any other object it can reach. This post builds that surface.</span></p><h2><strong><span>Expose the semantic layer as MCP</span></strong></h2><p style="text-align: justify;"><span>The idea is a small, customer-owned Databricks App that acts as an MCP server with a single job: answer </span><em><span>&#8220;which metric views match this ask, and how do I query them?&#8221;</span></em></p><p style="text-align: justify;"><span>At startup, the app builds an in-memory index of trimmed metric-view metadata: each view&#8217;s name, description, measures, and dimensions. It exposes one MCP tool, search_metric_views. An agent calls it with a natural-language ask and gets back the matching metric views, their measures and dimensions, and an example query so the agent knows how to use them without having to learn the metric-view syntax on its own.</span></p><p style="text-align: justify;"><span>Two design choices matter:</span></p><ul><li><p style="text-align: justify;"><strong><span>It&#8217;s discovery, not execution.</span></strong><span> The app returns </span><em><span>which</span></em><span> metric views fit and an example of how to query them.</span></p></li><li><p style="text-align: justify;"><strong><span>You control what&#8217;s exposed.</span></strong><span> The app can index only </span><em><span>certified</span></em><span> metric views, so external agents see governed, production-ready definitions and nothing that&#8217;s still in development or QA.</span></p></li></ul><h2><strong><span>Architecture</span></strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fn3V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fn3V!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png 424w, https://substackcdn.com/image/fetch/$s_!fn3V!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png 848w, https://substackcdn.com/image/fetch/$s_!fn3V!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png 1272w, https://substackcdn.com/image/fetch/$s_!fn3V!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!fn3V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png" width="1456" height="728" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/af4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!fn3V!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png 424w, https://substackcdn.com/image/fetch/$s_!fn3V!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png 848w, https://substackcdn.com/image/fetch/$s_!fn3V!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png 1272w, https://substackcdn.com/image/fetch/$s_!fn3V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf4777d5-bd5f-49f2-b196-cb695caba028_1774x887.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 style="text-align: justify;"><span>Multiple agents, inside or outside Databricks, connect to one Databricks App, which acts as the MCP server and looks up the semantic layer in Unity Catalog. The pattern improves discoverability: the app returns matching metric definitions and an example query. The agent can take necessary actions to generate SQL or manage the semantic layer to avoid duplication, etc., based on the use case.</span></p><p style="text-align: justify;"><strong><span>Where does the metadata live?</span></strong></p><p style="text-align: justify;"><span>The index can live in a few places, and each earns its place in a different situation:</span></p><ul><li><p style="text-align: justify;"><strong><span>In-memory, inside the app</span></strong><span>: the simplest option, and the one this post uses. No extra infrastructure, and reads are fast because they never leave the process. The catch is that the index rebuilds on every restart; each running copy of the app maintains its own copy; definitions can go stale between refreshes; and search is at the keyword level. Enough for a bounded catalog and a single team.</span></p></li><li><p style="text-align: justify;"><strong><span>Metadata table via a SQL endpoint:</span></strong><span> fits a bounded catalog that changes rarely. Every app instance reads one shared, governed copy, refreshed by a scheduled job.</span></p></li><li><p style="text-align: justify;"><strong><span>Lakebase</span></strong><span>: fits when many app instances and agents hit the index at once, and you want durable, low-latency lookups with structured filters such as domain, owner, or certified status, with room to keep usage or audit state alongside it.</span></p></li><li><p style="text-align: justify;"><strong><span>Managed AI Search (vector) index</span></strong><span>: fits when search quality is the point, whether a large catalog or plain-language questions, where ranking by meaning beats keyword matching, at the cost of more infrastructure and an index to keep in sync.</span></p></li></ul><p style="text-align: justify;"><span>The right choice comes down to the size of the catalog, the number of requests per minute you expect, and the amount of latency you can tolerate.</span></p><h2><strong><span>Governance</span></strong></h2><p style="text-align: justify;"><span>The app runs under a service principal, and you govern what it can reach with Unity Catalog grants (for example, restricting it to certified metric views). This fits multi-agent and cross-environment scenarios where the calling agents don&#8217;t carry a Databricks user identity.</span></p><p style="text-align: justify;"><span>If you need user-level access control, on-behalf-of (OBO) user authorization is supported. The agent acts as the calling user, and row and column governance is preserved end-to-end. It adds identity-passing complexity and isn&#8217;t required for the service-principal pattern this post uses.</span></p><h2><strong><span>Prerequisites</span></strong></h2><p><span>Before you build the app, get a few things in place:</span></p><p><span>1. Databricks workspace</span></p><ul><li><p><span>Active Databricks workspace</span></p></li><li><p><span>One or more Unity Catalog metric views</span></p></li></ul><p><span>2. An MCP-capable agent or client: anything that speaks the Model Context Protocol can connect and call the search tool.</span></p><h1><strong><span>Setup Instructions</span></strong></h1><blockquote><p><span>Step 1: Get the code</span></p><p><span>Download the five files from the repo folder:</span></p><p><a href="https://github.com/adgitdemo/ad_databricks/tree/main/mcp-metric-view-search"><span>https://github.com/adgitdemo/ad_databricks/tree/main/mcp-metric-view-search</span></a></p><p><span>You&#8217;ll have app.py, app.yaml, config.yaml, requirements.txt, and README.md.</span></p><p><span>Step 2: Point it at your warehouse and your data</span></p><p><span>Two files need real values before you deploy.</span></p><p><span>In app.yaml, set your SQL Warehouse ID. You&#8217;ll find it under SQL Warehouses &gt; your warehouse &gt; Connection details.</span></p><p><em><span>env:</span></em></p><p><em><span>  - name: DATABRICKS_WAREHOUSE_ID</span></em></p><p><em><span>    value: &#8220;your_warehouse_id&#8221;</span></em></p><p><em><span>In config.yaml, specify which catalogs and schemas the app should scan for metric views. Scope it down to keep the index focused, or leave it empty to scan everything the app can see.</span></em></p><p><em><span>scope:</span></em></p><p><em><span>  - catalog: your_catalog</span></em></p><p><em><span>    schemas:</span></em></p><p><em><span> - your_schema</span></em></p><p><span>Step 3: Deploy the app</span></p><p><span>Upload the folder to your workspace and deploy it. Deploying is what creates the app&#8217;s service principal, and that service principal is the identity you&#8217;ll grant data access to in the next step. So deploy first.</span></p><p><span>databricks workspace import-dir ./mcp-metric-view-search \</span></p><p><span>  /Workspace/Users/&lt;email id&gt;/mcp-metric-view-search --overwrite</span></p><p><span>databricks apps create metric-view-search-app</span></p><p><span>databricks apps deploy metric-view-search-app \</span></p><p><span>  --source-code-path /Workspace/Users/&lt;email id&gt;/mcp-metric-view-search</span></p><p><span>You can do the same thing from the Apps UI if you prefer.</span></p><p><span>Step 4: Find the app&#8217;s service principal</span></p><p><span>databricks apps get metric-view-search-app</span></p><p><span>Look at the service_principal_name field. It&#8217;ll be something like </span><em><span>xxxx-mcp-metric-view-search</span></em><span>. The app runs as this identity, so this is what needs to read your data, not your own user account.</span></p><p><span>Step 5: Grant the service principal access to your data</span></p><p><span>The app reads metric views through the warehouse, so the service principal needs three Unity Catalog grants plus warehouse access. Run each GRANT on its own. Databricks won&#8217;t accept several of them in a single statement.</span></p><p><span>GRANT USE CATALOG ON CATALOG your_catalog TO `xxxx-mcp-metric-view-search`;</span></p><p><span>GRANT USE SCHEMA ON SCHEMA your_catalog.your_schema TO `xxxx-mcp-metric-view-search`;</span></p><p><span>GRANT SELECT ON SCHEMA your_catalog.your_schema TO `xxxx-mcp-metric-view-search`;</span></p><p><span>Then give it the warehouse:</span></p><p><span>GRANT CAN USE ON WAREHOUSE `your_warehouse_id` TO `xxxx-mcp-metric-view-search`;</span></p><p><span>Repeat the schema grants for every schema you listed in config.yaml.</span></p><p><span>Step 6: Confirm it&#8217;s actually indexing</span></p><p><span>Redeploy so the app re-scans with its new permissions, then check any one of these:</span></p><p><span>- Open the app URL in a browser. The landing page shows how many metric views it has indexed.</span></p><p><span>- Hit the /health endpoint. It returns {&#8221;status&#8221;: &#8220;ok&#8221;, &#8220;indexed_metric_views&#8221;: N}.</span></p><p><span>- Read the app logs. You want to see the Indexed N metric view(s). If you instead see Cannot list catalogs or the scope still shows your_catalog_name, the config or the grants didn&#8217;t land.</span></p><p><span>Step 7: Let your teammates and their agents in</span></p><p><span>Everything above enables the app to read your data. To let other people connect their agents to it, grant them permission on the app itself. There are two ways to do this.</span></p><p><span>From the UI: open the app&#8217;s page, click Permissions in the top right, and add users, groups, or service principals. CAN USE is enough to connect and call the tools. CAN MANAGE also lets them redeploy and change settings.</span></p><p><span>Without the UI: use the permissions API. A PATCH adds people without disturbing anyone who already has access (a PUT would replace the entire list, so use PATCH here).</span></p><p><span>databricks api patch /api/2.0/permissions/apps/metric-view-search-app \</span></p><p><span>  --json &#8216;{</span></p><p><span>    &#8220;access_control_list&#8221;: [</span></p><p><span>      {&#8221;user_name&#8221;: &#8220;teammate@yourcompany.com&#8221;, &#8220;permission_level&#8221;: &#8220;CAN_USE&#8221;}</span></p><p><span>    ]</span></p><p><span>  }&#8217;</span></p><p><span>You can grant a group or another service principal the same way, and add several entries at once:</span></p><p><span>databricks api patch /api/2.0/permissions/apps/metric-view-search-app \</span></p><p><span>  --json &#8216;{</span></p><p><span>    &#8220;access_control_list&#8221;: [</span></p><p><span>      {&#8221;user_name&#8221;: &#8220;teammate@yourcompany.com&#8221;, &#8220;permission_level&#8221;: &#8220;CAN_MANAGE&#8221;},</span></p><p><span>      {&#8221;group_name&#8221;: &#8220;data-team&#8221;, &#8220;permission_level&#8221;: &#8220;CAN_USE&#8221;},</span></p><p><span>      {&#8221;service_principal_name&#8221;: &#8220;&lt;client-id&gt;&#8221;, &#8220;permission_level&#8221;: &#8220;CAN_USE&#8221;}</span></p><p><span>    ]</span></p><p><span>  }&#8217;</span></p><p><span>To confirm who has access, read the permissions back:</span></p><p><span>databricks api get /api/2.0/permissions/apps/metric-view-search-app</span></p><p><span>This is a different permission from Step 5. Step 5 controls what data the app can read. Step 7 controls who can use the app.</span></p><p><span>Step 8: Connect an agent</span></p><p><span>Register the app&#8217;s /mcp endpoint as an MCP server in your agent, give the connection a name like </span><em><span>metric-view-search</span></em><span>, and authenticate with a Databricks token.</span></p><p><span>For a connection that won&#8217;t expire after an hour, use a non-expiring Personal Access Token instead of a short-lived OAuth token. Then:</span></p><p><span>In the Claude Code CLI, that&#8217;s one command:</span></p><p><span>claude mcp add metric-view-search https://&lt;your-app-url&gt;/mcp \</span></p><p><span>  --header &#8220;Authorization: Bearer &lt;databricks-pat&gt;&#8221;</span></p><p><span>In a coded agent using the Claude Agent SDK, you configure the same three things (name, URL, and the auth header) in the mcpServers option, and add the tools to allowedTools so the agent can call them. See steps </span><a href="https://code.claude.com/docs/en/agent-sdk/mcp"><span>here</span></a><span> for wiring the app using the Claude Agent SDK:</span></p><p><span>options = ClaudeAgentOptions(</span></p><p><span>    mcp_servers={</span></p><p><span>        &#8220;metric-view-search&#8221;: {</span></p><p><span>            &#8220;type&#8221;: &#8220;http&#8221;,</span></p><p><span>            &#8220;url&#8221;: &#8220;https://&lt;your-app-url&gt;/mcp&#8221;,</span></p><p><span>            &#8220;headers&#8221;: {&#8221;Authorization&#8221;: &#8220;Bearer &lt;databricks-pat&gt;&#8221;},</span></p><p><span>        }</span></p><p><span>    },</span></p><p><span>    allowed_tools=[&#8221;mcp__metric-view-search__*&#8221;],</span></p><p><span>)</span></p><p><span>Either way, metric-view-search is just the connection name, and &lt;databricks-pat&gt; is the token. Once connected, the agent can search your metric views, read their definitions, and generate valid SQL. Other agents follow the same pattern.</span></p><p><span>&#9614; Note: a PAT is a standing personal credential that acts on your behalf until revoked. Fine for a demo; for shared or production use, prefer a service principal.</span></p><p><span>Other agents follow the same shape; they just differ in where you put the URL and the header. These steps may change based on your agent/tools.</span></p></blockquote><h2><strong><span>Demo</span></strong></h2><p style="text-align: justify;"><span>With a few metric views registered, an agent connected to the app can explore the semantic layer directly:</span></p><ul><li><p style="text-align: justify;"><span>Ask &#8220;what metric views do we have around revenue?&#8221; and the app returns the matching views with their measures and dimensions. Where definitions overlap (e.g., a fee-only revenue view and a blended-revenue view), both are surfaced with their descriptions so the agent or user can tell them apart.</span></p></li><li><p style="text-align: justify;"><span>For each match, the app returns an </span><strong><span>example query</span></strong><span> showing how to use the metric view.</span></p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FTbo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FTbo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png 424w, https://substackcdn.com/image/fetch/$s_!FTbo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png 848w, https://substackcdn.com/image/fetch/$s_!FTbo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png 1272w, https://substackcdn.com/image/fetch/$s_!FTbo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!FTbo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png" width="1215" height="446" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:446,&quot;width&quot;:1215,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!FTbo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png 424w, https://substackcdn.com/image/fetch/$s_!FTbo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png 848w, https://substackcdn.com/image/fetch/$s_!FTbo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png 1272w, https://substackcdn.com/image/fetch/$s_!FTbo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70ba9e54-b41c-4434-ad60-6d86a55a0343_1215x446.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hHbq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hHbq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png 424w, https://substackcdn.com/image/fetch/$s_!hHbq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png 848w, https://substackcdn.com/image/fetch/$s_!hHbq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png 1272w, https://substackcdn.com/image/fetch/$s_!hHbq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!hHbq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png" width="1091" height="639" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:639,&quot;width&quot;:1091,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!hHbq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png 424w, https://substackcdn.com/image/fetch/$s_!hHbq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png 848w, https://substackcdn.com/image/fetch/$s_!hHbq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png 1272w, https://substackcdn.com/image/fetch/$s_!hHbq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0e3b5-4355-4fb1-8ee5-2be1fc57f4e6_1091x639.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><strong><span>When do I need it?</span></strong></p><p style="text-align: justify;"><span>A few situations where searching the semantic layer directly is useful:</span></p><ul><li><p style="text-align: justify;"><span>Use a semantic layer from your own agents. You&#8217;ve standardized KPIs as metric views and want custom agents to use them without having to stand up additional consumer surfaces.</span></p></li><li><p style="text-align: justify;"><span>Partners and BI tools built on Databricks that want to explore available metrics programmatically to build more intelligent experiences.</span></p></li><li><p style="text-align: justify;"><span>Governing a large semantic layer. Data stewards find overlapping or duplicate metric views by keyword and owner across thousands of definitions, then audit and consolidate them.</span></p></li><li><p style="text-align: justify;"><span>Standardizing across a multicloud, multi-agent estate. Metric views are being open-sourced, so the same pattern can front a semantic layer wherever it runs.</span></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><item><title><![CDATA[Best practices for GPU Model Training on Databricks AI Runtime- Part 1 ]]></title><description><![CDATA[Practical guide for MLEs who are training models on GPUs]]></description><link>https://www.databricksters.com/p/best-practices-for-gpu-model-training</link><guid isPermaLink="false">https://www.databricksters.com/p/best-practices-for-gpu-model-training</guid><dc:creator><![CDATA[Gautam Srinivasan]]></dc:creator><pubDate>Fri, 24 Jul 2026 15:01:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6ruA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span><br></span><strong><span>TL;DR</span></strong></p><ul><li><p><span>Choose the right entry point: use notebooks with the </span><code>@distributed</code><span> decorator for rapid prototyping, and switch to the AIR CLI with YAML-based configs for long-running, multi-node production training.</span></p></li><li><p><span>Pick the right accelerator for the job: attach a low-cost A10 or single H100 for orchestration, and reserve multi-GPU or multi-node H100 configurations for actual training workloads.</span></p></li><li><p><span>Use </span><code>UCVolumeDataset</code><span> and </span><code>serverless_gpu.data.DataLoader</code><span> for efficient data loading with local caching, automatic distributed partitioning, and optimized prefetching keep GPUs fed without Spark overhead.</span></p></li><li><p><span>Enable MLflow autologging and monitor the System Metrics tab for GPU utilization and memory; set a custom run name and </span><code>MLFLOW_RUN_ID</code><span> to resume previous training runs.</span></p></li></ul><div><hr></div><p><span>This post covers the practical lessons we&#8217;ve learned from working with teams in Databricks and with customers shipping real GPU training workloads. Think of it as the &#8220;</span><em><span>things we wish someone had told us on day one</span></em><span>&#8221; guide.</span></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><span>If you&#8217;re training or fine-tuning deep learning models on Databricks, AI Runtime is how you get access to GPUs without the usual infrastructure headaches. But like any powerful tool, knowing how to use it well makes all the difference.</span></p><p><em><span>Update: You can find Part 2 of this blog </span><a href="https://www.databricksters.com/p/best-practices-for-gpu-model-training-1ad"><span>here</span></a></em><span>. </span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6ruA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6ruA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png 424w, https://substackcdn.com/image/fetch/$s_!6ruA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png 848w, https://substackcdn.com/image/fetch/$s_!6ruA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png 1272w, https://substackcdn.com/image/fetch/$s_!6ruA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6ruA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png" width="1456" height="694" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:694,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:201547,&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://www.databricksters.com/i/207084009?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.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_!6ruA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png 424w, https://substackcdn.com/image/fetch/$s_!6ruA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png 848w, https://substackcdn.com/image/fetch/$s_!6ruA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.png 1272w, https://substackcdn.com/image/fetch/$s_!6ruA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F655989a8-8356-4650-9964-80da3cf6cff4_1861x887.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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><strong><span>What is AI Runtime? How Can It Help?</span></strong></p><p><span>AI Runtime is a serverless model training product at Databricks built specifically for deep learning workloads. It brings GPU support to Databricks Serverless, so you can train and fine-tune models using your favorite frameworks (PyTorch, DeepSpeed, vLLM, Ray, etc.) without managing any infrastructure.</span></p><p><span>Before AI Runtime, running GPU workloads meant configuring clusters, picking the right drivers, managing autoscaling, and often negotiating GPU capacity directly with your cloud provider (AWS, Azure, or GCP). AI Runtime takes all of that off your plate.</span></p><p><strong><span>Key benefits:</span></strong></p><ul><li><p><strong><span>Fully managed GPU infrastructure</span></strong><span>: You request the GPUs you need and start working. Pay only for what you use. On-demand by nature.</span></p></li><li><p><strong><span>Seamless scaling</span></strong><span>. Go from a single GPU experiment to multi-node distributed training without reconfiguring anything.</span></p></li><li><p><strong><span>A runtime dedicated for deep learning</span></strong><span>: Choose either a minimal base environment for full control over your dependencies, or a full-featured AI environment pre-loaded with popular ML frameworks. Either way, CUDA, NCCL, and drivers are already set up. For multi-node H100s, RDMA networking is configured out of the box.</span></p></li><li><p><strong><span>Natively integrated</span></strong><span> across notebooks, jobs, Unity Catalog, and MLflow. Your development workflow, data access, experiment tracking, and governance all work together.</span></p></li></ul><div><hr></div><p><span>Here are the 5 AI Runtime best practices we will cover in this Part 1 blog. Feel free to jump directly to the section that&#8217;s interesting for you:</span></p><ul><li><p><span>What are my hardware accelerator options?</span></p></li><li><p><span>What are environments in AI Runtime?</span></p></li><li><p><span>What are the 3 ways of using AI Runtime- notebooks, CLI and IDE?</span></p></li><li><p><span>How can I use MLflow for tracing and observability?</span></p></li><li><p><span>What are the different options for data loading for training?</span></p></li></ul><p><strong><span>1. Hardware Accelerator Options</span></strong></p><p><span>The following </span><a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/#hardware-options"><span>accelerators</span></a><span> are currently supported. All accelerators provision a single node.</span></p><ul><li><p><code>1xA10</code><span>- 24 GB-Small to medium ML and deep learning tasks</span></p></li><li><p><code>1xH100</code><span>- 80 GB- Workloads that need more GPU memory than </span><code>1xA10</code><span> provides but do not require multi-GPU distributed training.</span></p></li><li><p><code>8xH100</code><span>- 80 GB per GPU- Large-scale AI workloads that need distributed training or multiple GPUs</span></p></li></ul><p><span>For multi-GPU workloads, choose a single-node </span><code>8xH100</code><span> instance (one node with 8 H100 GPUs). Partial multi-GPU configurations like </span><code>2xH100</code><span> or </span><code>4xH100</code><span> are not supported though. </span></p><p><span>For multi-node workloads, scale out using multiple </span><code>8xH100</code><span> nodes; for example, 4 nodes with 8 H100 GPUs each give you a total of 32 H100 GPUs. Or you can use multiple nodes of </span><code>1xA10</code><span>.</span></p><p><span>Besides the options listed above, be sure to consult the latest documentation, as additional accelerator types may become available over time.</span></p><p><strong><span>2. Environments</span></strong></p><p><span>AI Runtime offers two managed Python environments, the default base environment and the Databricks AI environment.</span></p><p><em><span>Default base environment</span></em></p><ul><li><p><span>Minimal, includes only required packages: torch, cuda, and torchvision</span></p></li><li><p><span>You want full control over your dependency stack and prefer to install only what you need</span></p></li></ul><p><em><span>Databricks AI environment</span></em></p><ul><li><p><span>Pre-loaded with popular ML frameworks (PyTorch, Transformers, and more)</span></p></li><li><p><span>You want a complete environment for training, fine-tuning, and experimentation without manual dependency management</span></p></li></ul><p><span>You can also use a </span><a href="https://docs.databricks.com/aws/en/compute/serverless/dependencies#base-environment"><span>workspace base environment</span></a><span> that a workspace admin has built for serverless GPU compute. <br><br></span><strong><span>Environment Caching</span></strong><span>: Environments are cached across sessions to speed up startup times. When you reconnect to AI Runtime with the same environment configuration, previously installed packages may be available from cache, reducing setup time. However, this behavior is not guaranteed, so always ensure your notebook includes the necessary %pip install commands for reproducibility..</span></p><p><strong><span>Import custom modules</span></strong><span>: Import custom modules by placing them under /Workspace/shared and adding the path to sys.path. Or You can also upload module files as Workspace files and import them directly.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;68bf4446-d01a-4627-ac51-cdd80c1cfde8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">import sys

sys.path.append(&#8221;/Workspace/Shared/my-project/src&#8221;)

from my_module import my_function</code></pre></div><p><span>Some differences as compared to Classic Compute:</span></p><ul><li><p><strong><span>Spark</span></strong><span>: AI Runtime is a Python-only environment; Spark is not available as a local runtime. So you cannot use PySpark functions but you can use Spark Connect for data loading.</span></p></li><li><p><strong><span>MLR</span></strong><span>: Pre-installed AI Runtime packages do not fully replace Databricks Runtime ML, because some ML libraries available in Databricks Runtime ML are not included.</span></p></li></ul><p><strong><span>3. Main Ways to Get Started: Notebooks, CLI and IDE</span></strong></p><p><span>You have two primary ways for running workloads on AI Runtime: </span><strong><span>Notebooks</span></strong><span> and </span><strong><span>AIR CLI</span></strong><span>.  Each has its strengths, and picking the right one for your use case makes the journey easier.</span></p><p><strong><span>3a. Notebooks</span></strong></p><p><span>Notebooks on AI Runtime let you develop interactively, iterate quickly, and visualize results in place. Simply choose your preferred accelerator (A10, 1&#215;H100, or 8&#215;H100), attach it to your notebook, and your code will execute directly on that GPU-backed node.</span></p><p><em><span>Multi-GPU (Single Node)</span></em></p><p><span>To run jobs across multiple GPUs on a single node, use the </span><code>@distributed</code><span> decorator. Wrap your training workflow inside a function and apply the decorator to it. This function becomes the entry point for distributed execution, so all the components - model initialization, data loading, and training logic - should be defined within it.<br>The </span><code>@distributed</code><span>  API integrates with major distributed training libraries: PyTorch, DDP, FSDP and DeepSpeed</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;6ac6784a-b8e3-4357-8fdc-8ef4d5e9b91b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">@distributed(gpus=8, gpu_type=&#8217;H100&#8217;)

def run_train(num_epochs, batch_size):

    # training code here


run_train.distributed(num_epochs=3, batch_size=1)
</code></pre></div><p><em><span>Ray</span></em></p><p><span>For a single node (A10 or 8xH100), you can just use </span><code>ray_init</code> from <code>serverless_gpu</code></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;2ead0e5e-fee5-40bc-a2a2-fe9e553f6618&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from serverless_gpu import ray_init; 

ray_init()</code></pre></div><p>For multi-node, use <code>air</code> CLI.</p><p><span>Best practices:</span></p><ul><li><p><span>Pin your library versions: AI Runtime has regular releases. If you need reproducibility, pin the versions that work for your code.</span></p></li><li><p><span>For distributed training, any environment variable needed by your training code should be set within </span><code>@distributed</code><span>. Note: Some environment variables must be set before importing a particular library so that it initializes with the correct configuration and hardware settings. This is specific to the library, not AI runtime.</span></p></li><li><p><span>Keep experimentation in notebooks, production in jobs: Notebooks are great for prototyping and debugging, but once your code is stable, you can move it to a scheduled job. For scheduling notebook jobs, use the Jobs API or Declarative Automation Bundles (fka Databricks Asset Bundles)</span></p></li><li><p><span>For operations that do not require GPUs (for example, converting data formats, or exploratory data analysis), attach your notebook to a CPU cluster to preserve GPU resources.</span></p></li><li><p><span>Note: Connection to your interactive compute auto-terminates after 60 minutes of inactivity.</span></p></li></ul><p><strong><span>3b. AI Runtime CLI</span></strong></p><p><code>air</code><span> CLI is a command-line tool for submitting and managing training jobs on Databricks Serverless GPU compute directly from your terminal, instead of using notebooks.The CLI uses YAML-based job configuration, and supports workspace-based and git-based code workflows. It&#8217;s aimed for users who prefer repos, IDEs, git, and CI/CD-style workflows over notebooks.</span></p><p><span>air CLI follows a simple </span><em><span>submit &#8594; schedule &#8594; execute &#8594; observe</span></em><span> loop:</span></p><ol><li><p><strong><span>Define</span></strong><span> your workload in a </span><code>train.yaml</code><span> file (compute shape, environment, launch command).</span></p></li><li><p><strong><span>Submit</span></strong><span> via </span><code>air run -f workload.yaml</code></p></li><li><p><strong><span>AIR automatically provisions</span></strong><span> the requested GPU nodes, installs dependencies from requirements.yaml, and injects coordination environment variables</span></p></li><li><p><strong><span>Your training code runs</span></strong><span> (e.g., via torchrun) across the provisioned nodes.</span></p></li><li><p><strong><span>Observe</span></strong><span> progress through  </span><code>air get logs</code><span>,  </span><code>air get status</code><span>, or the MLflow UI for GPU metrics.</span></p></li></ol><p><span>Here is a simple </span><code>train.yaml</code></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;yaml&quot;,&quot;nodeId&quot;:&quot;4e6611b8-e989-416b-9aee-159ea3e6d6a2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-yaml">experiment_name: simple-training

environment:

  dependencies: requirements.yaml

env_variables:

  NCCL_DEBUG: &#8220;INFO&#8221;

secrets:

  HF_TOKEN: &#8216;my_scope/hf_token&#8217;

compute:

  num_accelerators: 8

  accelerator_type: GPU_8xH100

code_source:

  type: snapshot

    snapshot:

      root_path: /home/username/repo

      git:

        branch: main

command: torchrun --nproc_per_node=8 train.py

max_retries: 2

timeout_minutes: 90

usage_policy_id: abcd123-25b8-3e87-9a2c-f86eb19d101c</code></pre></div><p>Here is the <a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/cli/">doc</a> for AI CLI for more information.</p><p><strong><span>Best practices for AIR CLI:</span></strong></p><ul><li><p><span>Use CLI for multi-node, long-duration jobs: If your training run takes hours or days and spans multiple nodes, AIR CLI is the right tool. It handles the orchestration cleanly.</span></p></li><li><p><span>Treat the workload.yaml as the source of truth. Keep the launch definition in version-controlled YAML rather than relying on notebook/UI state, so runs are easier to reproduce, hand off, and compare later.</span></p></li><li><p><span>Check </span><code>workload.yaml</code><span> and requirements.yaml into your repo along with </span><code>train.py</code><span> for version tracking and reproducibility.</span></p></li></ul><p><a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/cli/docker-images"><span>Custom Docker Images</span></a><span>: You can run your own Docker images with AI Runtime using a simple CLI and YAML-based configuration. This is useful for workloads that need specific libraries, complex dependencies, fully reproducible environments, or standard images defined by your organization.</span></p><p><strong><span>When to use AIR CLI vs. Notebooks:</span></strong></p><p><span>Use notebooks (with the </span><code>@distributed</code><span> decorator) for rapid iteration and prototyping.</span></p><p><span>Use the AI Runtime CLI when you need to:</span></p><ul><li><p><span>Submit GPU training jobs directly from your terminal.</span></p></li><li><p><span>Define jobs in YAML for version control and reproducibility.</span></p></li><li><p><span>Run long (multi-day) or distributed (multi-node) training that shouldn&#8217;t depend on an interactive session.</span></p></li></ul><p><strong><span>3c. Connecting from the IDE terminal like Visual Studio or Cursor</span></strong></p><p><span>You can connect to AI Runtime on serverless GPU compute directly from a terminal in your IDE through an SSH tunnel.</span></p><p><span>To connect from a terminal within your IDE, run the databricks ssh connect command with the --accelerator option.</span></p><p><code>databricks ssh connect --accelerator=GPU_1xA10</code></p><p><span>To connect and start the session in Visual Studio Code or Cursor, use the </span><code>--ide</code><span> option. The CLI opens an IDE window pointing to the home workspace folder.</span></p><p><code>databricks ssh connect --ide=vscode</code></p><p><strong><span>4. Use MLflow for Tracking and Observability</span></strong></p><p><span>On AI Runtime, MLflow is the main place to track and inspect training runs. AIR is natively integrated with MLflow for experiment tracking, model logging, metric visualization, and observability.</span></p><p><span>It&#8217;s recommended to use the latest version of </span><code>mlflow</code><span> (minimum 3.7 version)</span></p><p><span>For each run of your experiment:</span></p><ul><li><p><span>The </span><strong><span>System Metrics</span></strong><span> tab provides an overview of GPU, CPU, and disk performance, including key metrics such as utilization and memory usage. These are generated automatically for each run. Use these metrics to proactively identify bottlenecks, tune workloads (for example by adjusting batch size or data loading), and ensure resources are right-sized so you maintain high utilization without over-provisioning</span></p></li><li><p><span>The </span><strong><span>Model Metrics</span></strong><span> tab surfaces key performance indicators logged during training, such as accuracy, loss, and other task-specific scores. Use these metrics to validate and compare experiments, ensuring that changes to model architecture, training configuration (for example, batch size, learning rate, and data preprocessing),etc actually improve model quality over time/</span></p></li><li><p><strong><span>Logs</span></strong><span> for processes running on each GPU are stored as artifacts and can be accessed from the MLflow UI. For notebook-based runs, the same logs are also visible directly in the notebook cell outputs. Use these logs to monitor training progress, debug training issues, and trace performance regressions across runs so you can quickly identify and fix problems in your training pipeline.</span></p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2eTK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2eTK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png 424w, https://substackcdn.com/image/fetch/$s_!2eTK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png 848w, https://substackcdn.com/image/fetch/$s_!2eTK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png 1272w, https://substackcdn.com/image/fetch/$s_!2eTK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!2eTK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png" width="1456" height="1070" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1070,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!2eTK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png 424w, https://substackcdn.com/image/fetch/$s_!2eTK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png 848w, https://substackcdn.com/image/fetch/$s_!2eTK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png 1272w, https://substackcdn.com/image/fetch/$s_!2eTK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffab97a-e6fa-49ae-9f26-6363b63b5f0a_2048x1505.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 style="text-align: center;"><span>System Metrics</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bgx7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bgx7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png 424w, https://substackcdn.com/image/fetch/$s_!bgx7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png 848w, https://substackcdn.com/image/fetch/$s_!bgx7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png 1272w, https://substackcdn.com/image/fetch/$s_!bgx7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!bgx7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png" width="1456" height="842" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:842,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!bgx7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png 424w, https://substackcdn.com/image/fetch/$s_!bgx7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png 848w, https://substackcdn.com/image/fetch/$s_!bgx7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png 1272w, https://substackcdn.com/image/fetch/$s_!bgx7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5327686b-556b-4148-ad9b-24e88fd929f0_2048x1185.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 style="text-align: center;"><span>Logs are also available as artifacts in the MLflow run</span></p><p><strong><span>Best practices:</span></strong></p><ul><li><p><span>Autologging: MLflow supports automatic logging for popular frameworks like PyTorch, TensorFlow etc. You can enable it for your training job by adding just one of code,  </span><code>mlflow.autolog()</code><span>. It automatically captures:</span></p><ul><li><p><span>Metrics: standard training and evaluation measures (such as accuracy and F1 score);</span></p></li><li><p><span>Parameters: hyper-parameters, such as learning rate and number of estimators</span></p></li><li><p><span>Artifacts: output files, such as your trained model weights.</span></p></li></ul></li><li><p><span>Manual Logging: you can manually log any parameter, metric or artifact in your training code.</span></p></li><li><p><span>The Serverless GPU API automatically launches an MLflow experiment with default name /Users/{WORKSPACE_USER}/{get_notebook_name()}. To overwrite it, set</span></p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;7dda7fed-8376-4948-ae12-fd486f924966&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import os

os.environ[&#8221;MLFLOW_EXPERIMENT_NAME&#8221;] = &#8220;/Users/&lt;username&gt;/my-experiment&#8221;</code></pre></div><ul><li><p><span>It&#8217;s recommended to customize the MLflow run name. It enables you to restart from a previous run.You can customize the run name using the run_name parameter in  </span><code>mlflow.start_run(run_name=&#8221;your-custom-name)</code><span> or in third-party libraries that support MLflow (for example, Hugging Face Transformers). Otherwise, the default run name is </span><code>jobTaskRun-xxxxx</code><span>.</span></p></li><li><p><span>Resume previous training by setting the MLFLOW_RUN_ID from the earlier run: </span><code>mlflow.start_run(run_id=&#8221;&lt;previous-run-id&gt;&#8221;)</code></p></li><li><p><span>Set the `step` parameter in </span><em><span>MLFlowLogger</span></em><span> to reasonable batch numbers. MLflow has a limit of 10 million metric steps, so logging every single batch on large training runs can hit this limit</span></p></li></ul><p><strong><span>Monitor GPU resources</span></strong><span>:</span></p><p><span>If you are using Notebook, you can use </span><strong><span>GPU resources</span></strong><span> in the right side pane to monitor GPU health and utilization while your code runs on AI Runtime. The pane supports both single-node and multi-node workloads. The pane displays the following metrics for each GPU: GPU utilization percentage, GPU memory usage, Temperature.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yF6p!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yF6p!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png 424w, https://substackcdn.com/image/fetch/$s_!yF6p!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png 848w, https://substackcdn.com/image/fetch/$s_!yF6p!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png 1272w, https://substackcdn.com/image/fetch/$s_!yF6p!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!yF6p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png" width="1388" height="1829" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1829,&quot;width&quot;:1388,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!yF6p!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png 424w, https://substackcdn.com/image/fetch/$s_!yF6p!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png 848w, https://substackcdn.com/image/fetch/$s_!yF6p!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png 1272w, https://substackcdn.com/image/fetch/$s_!yF6p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc82c126e-b0a1-4dc9-8531-434aa628f871_1388x1829.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 style="text-align: center;">GPU metrics</p><p><span>If you are using CLI, you can utilize System Metrics to view GPU metrics.</span></p><p><strong><span>5. Data Loading</span></strong></p><p><strong><span>Tabular Data:</span></strong></p><ul><li><p><span>Small Dataset: For small Delta tables, use Spark Connect to load the data. Then convert the Spark DataFrame to a pandas DataFrame with </span><em><span>toPandas()</span></em><span>. If needed, you can further convert it to a NumPy array using </span><em><span>to_numpy()</span></em><span>. This works well for Single Node</span></p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;36663fa1-dbd7-465d-bc8d-e0d29f4f7193&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Load Delta table

df = spark.read.format(&#8221;delta&#8221;).table(&#8221;your_catalog.your_schema.your_table&#8221;)

# Convert Spark DataFrame to pandas

pdf = df.toPandas()

# Optional: convert pandas DataFrame to NumPy array

arr = pdf.to_numpy()</code></pre></div><ul><li><p><span>Large dataset: For large Delta tables that are too big to convert with </span><em><span>toPandas()</span></em><span>, export the data to a Unity Catalog volume and load it directly using PyTorch or Hugging Face. This approach avoids Spark overhead during training and works well for both single-GPU and distributed training workflows.</span></p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;1ce539ae-0e98-4dc6-b262-cc97a811d12f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Step 1: Export the Delta table to Parquet files in a UC volume

output_path = &#8220;/Volumes/catalog/schema/my_volume/training_data&#8221;

spark.table(&#8221;catalog.schema.my_table&#8221;).write.mode(&#8221;overwrite&#8221;).parquet(output_path)

# Step 2: Load the exported data directly using Hugging Face datasets

from datasets import load_dataset

dataset = load_dataset(&#8221;parquet&#8221;, data_files=&#8221;/Volumes/catalog/schema/my_volume/training_data/*.parquet&#8221;)</code></pre></div><p><strong><span>Unstructured Data</span></strong><span>:</span></p><p><span>For unstructured data such as images, audio, and text files stored in Unity Catalog volumes, our recommendation is to use </span><em><span>UCVolumeDataset</span></em><span> from the </span><em><span>serverless_gpu.data</span></em><span> package. </span></p><p><em><span>UCVolumeDataset</span></em><span> is a PyTorch </span><em><span>IterableDataset</span></em><span> that copies each file from the volume to a fast local cache on first access and yields the cached local file path. Besides local caching it supports automatic partitioning of files and divided across DataLoader workers, so each (rank, worker) pair receives a non-overlapping slice.</span></p><p><span>For optimal performance, pair </span><em><span>UCVolumeDataset</span></em><span> with </span><em><span>serverless_gpu.data.DataLoader</span></em><span>, rather than the stock PyTorch </span><em><span>DataLoader</span></em><span>, as it is tuned for serverless GPU I/O and fetches and caches files concurrently while the GPU computes.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;0c006d7c-cb78-4cd7-98a3-caa01cdfe0fd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from serverless_gpu.data import UCVolumeDataset

from torch.utils.data import IterableDataset

from PIL import Image

import torchvision.transforms.functional as TF

class ImageDataset(IterableDataset):

    &#8220;&#8221;&#8220;Decodes each cached file path from UCVolumeDataset into a tensor.&#8221;&#8220;&#8221;

    def __init__(self, path_dataset: UCVolumeDataset):

        self._path_dataset = path_dataset

    def __iter__(self):

        for local_path in self._path_dataset:

            image = Image.open(local_path).convert(&#8221;RGB&#8221;)

            yield TF.to_tensor(image)

path_dataset = UCVolumeDataset(&#8221;/Volumes/catalog/schema/my_volume/images&#8221;)

dataset = ImageDataset(path_dataset)</code></pre></div><p><span>For more info about data loading, here is the </span><a href="https://docs.databricks.com/aws/en/machine-learning/ai-runtime/dataloading"><span>doc</span></a><span>.</span></p><p><strong><span>Best Practices:</span></strong></p><ul><li><p><span>Load data inside the </span><em><span>@distributed</span></em><span> decorator: For distributed training, move data loading code inside the @distributed decorator. The dataset size can exceed the maximum size allowed by pickle, so it is recommended to generate the dataset inside the decorator.</span></p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;a9317af1-8f44-4f86-ab5a-17ec9d425a0d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from serverless_gpu import distributed

# This may cause a pickle error if the dataset is too large

dataset = get_dataset(file_path)

@distributed(gpus=8, gpu_type=&#8217;H100&#8217;)

def run_train():

   # Load data inside the decorator to avoid pickle serialization issues

   dataset = get_dataset(file_path)

   ...</code></pre></div><p><span>When you build a </span><em><span>UCVolumeDataset</span></em><span> in the decorator, it uses torch.distributed rank info at runtime to automatically split files across ranks, so you don&#8217;t need a DistributedSampler for file-based volume data.</span></p><ul><li><p><span>Use </span><em><span>UCVolumeDataset</span></em><span> for multi-epoch training so files are cached locally on first access and reused, instead of copying the entire volume upfront with shutil.copytree when you may only read part of it.</span></p></li></ul><ul><li><p><span>Cache data locally for multi-epoch training. For large datasets, copy datasets to /tmp for faster access across epochs:</span></p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;09c5c85b-9976-4732-a0e3-99a5cbbb0194&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import shutil
shutil.copytree(&#8221;/Volumes/catalog/schema/volume/dataset&#8221;, &#8220;/tmp/dataset&#8221;)</code></pre></div><ul><li><p><span>Parallelize data fetching: Use </span><em><span>serverless_gpu.data.DataLoader</span></em><span> to parallelize fetching. It&#8217;s a torch DataLoader subclass optimized for serverless GPUs, with higher defaults for worker count and prefetching so files are loaded and cached while the GPU runs. It also records per-batch load times in MLflow to help identify data bottlenecks.</span></p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;0493d348-b04c-4c5c-b627-38558fdde86e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from serverless_gpu.data import DataLoader

loader = DataLoader(

    dataset,

    batch_size=32,

    pin_memory=True,

    # num_workers=6, by default

    # prefetch_factor=4, by default

    # raise num_workers to increase parallel reads, or prefetch_factor to deepen each worker&#8217;s queue.

)</code></pre></div><ul><li><p><span>Adjust batch size: For large datasets, use a larger batch size so the cost of loading data per batch is spread over more samples, reducing file fetches each step. If GPU memory is tight, pair a larger batch with gradient accumulation to keep the same effective batch size.</span></p></li></ul><ul><li><p><span>Use Streaming datasets: For very large datasets that do not fit in memory, use streaming approaches.</span></p><ul><li><p><em><span>UCVolumeDataset</span></em><span> from </span><em><span>serverless_gpu.data</span></em><span> for streaming files from Unity Catalog volumes with local caching and automatic distributed partitioning.</span></p></li><li><p><span>PyTorch </span><em><span>IterableDataset</span></em><span> for custom streaming logic.</span></p></li><li><p><span>Hugging Face datasets with streaming for datasets hosted on the Hub or in volumes.</span></p></li><li><p><span>RayData for distributed batch data processing</span></p></li></ul></li></ul><div><hr></div><h2><strong><span>FAQs</span></strong></h2><p><strong><span>Q: My training run is slow and GPU utilization is low. What should I check first?</span></strong></p><p><span>A: Start by inspecting the </span><strong><span>System Metrics</span></strong><span> tab in MLflow, </span>focusing on GPU utilization, GPU memory usage, and data loading throughput. If GPU utilization is low while the GPU is not memory-bound, your data pipeline is likely the bottleneck. <span>We recommend increasing your batch size or switching to </span><code>serverless_gpu.data.DataLoade</code><span>r, which is specifically tuned for serverless I/O. Additionally, ensure you are using </span><code>UCVolumeDataset</code><span> to cache files locally instead of refetching them from Unity Catalog during every step.</span></p><p><strong><span>Q: Should I use notebooks or the AIR CLI for my training workload?</span></strong></p><p><span>A: It depends on your workflow phase. Use notebooks with the </span><em><span>@distributed</span></em><span> decorator for rapid prototyping and interactive debugging. Switch to the AIR CLI when you need production-grade features like version-controlled YAML configurations, multi-day training runs, or a CI/CD-style approach directly from your IDE.</span></p><p><strong><span>Q: How do I resume a previous MLflow training run?</span></strong><span> <br></span><strong><span>A:</span></strong><span> Set a custom run name using </span><em><span>mlflow.start_run(run_name=&#8221;your-custom-name&#8221;)</span></em><span> so you can identify the run later. To resume, call </span><em><span>mlflow.start_run(run_id=&#8221;&lt;previous-run-id&gt;&#8221;) </span></em><span>with the earlier run&#8217;s ID. This lets you continue logging metrics and artifacts into the same run record.</span></p><p><strong><span>Q: Which environment should I choose &#8212; the base environment or the AI environment? </span></strong><span><br>A: Start with the AI environment (recommended) as it has popular ML frameworks (PyTorch, DeepSpeed, vLLM, Ray, etc.) pre-installed. </span>Use the base environment if you need full control over your dependencies and want a minimal setup. You can also use a base environment defined by your workspace admin, built on organization-standard images<span>. Always pin library versions for reproducibility.</span></p><div><hr></div><p><span>Thanks for reading. Feel free to share any feedback on topics you&#8217;d like to see next in the comments section.</span></p><p><span>Shout out to the AI Runtime PM, </span><a href="https://www.linkedin.com/in/tejassundaresan/"><span>Tejas Sundaresan</span></a><span> for helping us review the content in this blog.</span></p><p><span>We plan to work on Part 2 for this AI Runtime blog series next. In this post, we will share some more best practices around remote development options, cost and usage monitoring, and using genie code with AI Runtime. Stay tuned!</span></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.com/p/best-practices-for-gpu-model-training/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.databricksters.com/p/best-practices-for-gpu-model-training/comments"><span>Leave a comment</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[The Spark Best Practices You Learned Are Already Out of Date]]></title><description><![CDATA[Why traditional tuning wisdom is slowing down your pipelines, and how to optimize high-throughput for modern NVMe and silicon]]></description><link>https://www.databricksters.com/p/the-spark-best-practices-you-learned</link><guid isPermaLink="false">https://www.databricksters.com/p/the-spark-best-practices-you-learned</guid><dc:creator><![CDATA[Canadian Data Guy]]></dc:creator><pubDate>Tue, 14 Jul 2026 15:02:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!M29Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My colleague and I spent the past few weeks hyper-tuning a Spark streaming workload &#8212; millions of rows per second, at the lowest cost we could manage, without breaking our latency SLA. What we found didn&#8217;t match what either of us expected going in.</p><p>Here are five lessons we learned over a week of benchmarking on Spark 4</p><h2>Rule 1: Upgrade the Silicon (M8/M9, Not M5/M6)</h2><p>Before tuning shuffle partitions or threading, pick the right generation.</p><p>On the same workload &#8212; same Databricks Runtime, same node count, same pipeline &#8212; we moved from M6gd to M8gd and saw a rare three-way win:</p><ul><li><p>2&#215; throughput (more records per second)</p></li><li><p>~38% lower cost per record processed</p></li><li><p>Lower batch latency end to end</p></li></ul><p>Newer Graviton generations (M8, and M9 when available) bring better CPU, memory bandwidth, and local NVMe behavior. That matters most when every micro-batch does shuffle, spill, and multi-table writes.</p><p><strong>Practice:</strong> Don&#8217;t lock onto M5/M6 families because they&#8217;re familiar. Benchmark newest generation up on the same job before you optimize code. Often the biggest lever is hardware, not another <code>spark.conf</code> tweak.</p><h2>Rule 2: Multi-Thread Your foreachBatch Sinks</h2><p><code>foreachBatch</code> runs once per micro-batch. If you write to more than one table (main, audit, dead-letter), sequential writes leave the cluster idle between commits.</p><p><strong>Practice:</strong> Do transforms once, split the batch, submit writes in parallel from the driver.</p><pre><code><code>from concurrent.futures import ThreadPoolExecutor

def process_batch(batch_df, batch_id):
    with ThreadPoolExecutor(max_workers=2) as executor:
        futures = [
            executor.submit(write_to_table_a, batch_df, batch_id),
            executor.submit(write_to_table_b, batch_df, batch_id),
        ]
        for f in futures:
            f.result()</code></code></pre><p>Parallel writes mean more Spark jobs per batch &#8212; you&#8217;re trading wall-clock time for driver coordination. Profile before raising <code>max_workers</code>.</p><p>For a fuller production version of this &#8212; caching the batch once, fanning out to N tables instead of 2, propagating thread exceptions properly &#8212; see <a href="https://github.com/jiteshsoni/material_for_public_consumption/blob/main/notebooks/parallelize_for_each_batch.py">the reference notebook</a>. Two companion videos walk through it: <a href="https://www.youtube.com/watch?v=gur1oLe5t2o">foreachBatch fundamentals</a> first, then <a href="https://www.youtube.com/watch?v=n9jodzYq1e4">parallelizing the writes</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!M29Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!M29Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png 424w, https://substackcdn.com/image/fetch/$s_!M29Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png 848w, https://substackcdn.com/image/fetch/$s_!M29Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png 1272w, https://substackcdn.com/image/fetch/$s_!M29Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!M29Y!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png" width="1200" height="799.4505494505495" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bff96e35-23b8-461c-942f-9e4d28055969_2528x1684.jpeg&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:970,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:2073399,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.databricksters.com/i/204337804?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbff96e35-23b8-461c-942f-9e4d28055969_2528x1684.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!M29Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png 424w, https://substackcdn.com/image/fetch/$s_!M29Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png 848w, https://substackcdn.com/image/fetch/$s_!M29Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png 1272w, https://substackcdn.com/image/fetch/$s_!M29Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f8bcfab-ca69-4db4-ad8f-4b331cb7c218_2528x1684.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2>Rule 3: Spill to Local Disk, Don&#8217;t Recache What&#8217;s Already There</h2><p>Every task writes its output to the disk on the machine it runs on. You can let that spill hit the network over EBS, or you can pick an instance family with disk physically attached to the node. The attached disk wins on performance, every time.</p><p>Default is simple: don't cache. When the fork happens after a shuffle &#8212; the batch already passed through a join, aggregation, or stateful dedup &#8212; the shuffle write already left a durable copy on local disk that Spark reuses automatically for any second write path, retry or independent action alike (look for a "skipped" stage in the Spark UI), so caching here only duplicates what's already there.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K66N!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K66N!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png 424w, https://substackcdn.com/image/fetch/$s_!K66N!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png 848w, https://substackcdn.com/image/fetch/$s_!K66N!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png 1272w, https://substackcdn.com/image/fetch/$s_!K66N!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!K66N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png" width="1456" height="495" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:495,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50378,&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://www.databricksters.com/i/204337804?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.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_!K66N!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png 424w, https://substackcdn.com/image/fetch/$s_!K66N!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png 848w, https://substackcdn.com/image/fetch/$s_!K66N!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png 1272w, https://substackcdn.com/image/fetch/$s_!K66N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceeb4593-abb4-4da0-9f0a-ff4e1dfb4687_1472x500.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 judgment call lives in the other case: a fork before any shuffle, where a shared read and a few light transforms split straight into two writes with no durable file to fall back on. Skip the cache and both paths redo that shared work from scratch, source read included; cache it and you pay a serialize-then-deserialize round trip through the block manager instead. Which one wins depends on the weight of the shared transforms &#8212; a wide UDF or an expensive parse earns the cache, a couple of column selects don&#8217;t, and recomputing them twice is cheaper than the round trip.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CHHq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CHHq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png 424w, https://substackcdn.com/image/fetch/$s_!CHHq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png 848w, https://substackcdn.com/image/fetch/$s_!CHHq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png 1272w, https://substackcdn.com/image/fetch/$s_!CHHq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!CHHq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png" width="1456" height="506" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:506,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:44649,&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://www.databricksters.com/i/204337804?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.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_!CHHq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png 424w, https://substackcdn.com/image/fetch/$s_!CHHq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png 848w, https://substackcdn.com/image/fetch/$s_!CHHq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png 1272w, https://substackcdn.com/image/fetch/$s_!CHHq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90060a68-f6bc-4cbc-aa52-8721959cc958_1472x512.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>Here&#8217;s the number that makes this rule worth enforcing: memory-optimized (R-series) instances carry at least a <strong>~20% price premium over general-purpose compute &#8212; on every major cloud, not just one</strong>. Cut the unnecessary caching and you cut the memory pressure that pushes you onto R-series in the first place. That 20% doesn&#8217;t vanish &#8212; you either bank it as savings or spend it on more CPU. Cache when profiling proves it pays off, not by habit.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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">Dozens of hours, endless caffeine, and millions of rows processed&#8212;all distilled into a quick read. Subscribe to support the grind. No paywalls, no spam, just pure takeaways.</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><h2>Rule 4: Optimize for Finishing on Time, Not Just Finishing</h2><p>In a latency-bound streaming job, &#8220;did the job succeed&#8221; is the wrong question. The right one i<em>s &#8220;did it finish inside the SLA.&#8221;</em> A batch that completes in 90 seconds against a 60-second budget hasn&#8217;t crashed &#8212; it&#8217;s succeeded and missed the point.</p><p>That reframes how you pick a join strategy. Sort-merge joins are stable: they spill to disk gracefully and rarely blow up outright. But they don&#8217;t respond to more hardware as easily &#8212; you can&#8217;t throw machines at a sort-merge join and expect a proportional speedup. (<a href="https://www.canadiandataguy.com/p/spark-join-strategies-explained-sort">Full mechanics here.</a>)</p><p>Shuffle hash joins do respond to more hardware. They&#8217;re less forgiving under memory pressure, but they scale with compute in a way sort-merge doesn&#8217;t. On an SLA-bound job, that tunability is worth more than the extra stability. (<a href="https://www.canadiandataguy.com/p/spark-join-strategies-explained-shuffle">Full breakdown here.</a>)</p><p><strong>Practice:</strong> If your streaming job has an SLA attached, treat join strategy as a lever you tune on purpose &#8212; not whatever Spark defaults to.</p><h2><strong>Rule 5: Set shuffle partitions to match cluster cores</strong></h2><blockquote><p>Highly discouraged, so unless you absolutely need to, you can just let auto do it for you </p><p>The only reason we set this is because we were heavily latency-bound and did not want to leave it to the auto algorithm. That means we were overly conservative.</p></blockquote><p>On join-heavy streams, <code>spark.sql.shuffle.partitions</code> drives task size and scheduler load.</p><ul><li><p>Too <strong>low</strong> &#8594; fat tasks, spill, OOM</p></li><li><p>Too <strong>high</strong> &#8594; tiny tasks, overhead, small files<br></p></li></ul><h2><strong><span>Recommended Companion Reads</span></strong></h2><p><span>Now that you&#8217;ve upgraded your hardware and parallelized your sinks, here is how to take your high-performance streaming architecture to the next level:</span></p><ol><li><p><strong><span>Optimize Your Compute Storage:</span></strong><span> Moving beyond default node disk settings can dramatically reduce streaming latency. Learn how to </span><a href="https://docs.databricks.com/aws/en/admin/clusters/manage-ssd"><span>Deploy and Manage Local SSDs on AWS</span></a><span> or upgrade your block storage setup with </span><a href="https://www.databricks.com/blog/2021/08/10/introducing-support-for-gp3-amazons-new-general-purpose-ssd-volume.html"><span>GP3 Support on Databricks</span></a><span> to instantly maximize write IOPS.</span></p></li><li><p><strong><span>Master Advanced Join Tactics:</span></strong><span> Choosing the wrong join strategy can derail your SLA during transaction spikes. Read our step-by-step guides on </span><a href="https://www.canadiandataguy.com/p/spark-join-strategies-explained-shuffle"><span>How Shuffle Hash Joins Scale</span></a><span> and why traditional </span><a href="https://www.canadiandataguy.com/p/spark-join-strategies-explained-sort"><span>Sort-Merge Joins Might Limit Your Streaming Performance</span></a><span>.</span></p></li><li><p><strong><span>Download the Implementation Notebook:</span></strong><span> Skip the boilerplate. Grab our ready-to-import </span><a href="https://github.com/jiteshsoni/material_for_public_consumption/blob/main/notebooks%2Fparallelize_for_each_batch.py"><span>Parallel ForeachBatch Script</span></a><span> from GitHub, and check out the walkthrough videos on </span><a href="https://www.youtube.com/watch?v=gur1oLe5t2o"><span>ForeachBatch Parallelization (Part 1)</span></a><span> and </span><a href="https://www.youtube.com/watch?v=n9jodzYq1e4&amp;t=5s"><span>ThreadPool Tuning (Part 2)</span></a><span>.</span></p></li></ol><h2><strong>FAQ</strong></h2><p><strong>When should I parallelize </strong><code>foreachBatch</code><strong> writes?</strong></p><p>When a single micro-batch writes to multiple independent sinks, such as a main table and a dead-letter path. Parallelization helps when sink writes are a meaningful share of batch duration and the driver can coordinate the extra jobs safely.</p><p><strong>Is caching inside </strong><code>foreachBatch</code><strong> usually a good idea?</strong></p><p>No. In many streaming pipelines, especially after joins or aggregations, Spark has already materialized shuffle output. Adding <code>.cache()</code> can increase memory use and GC overhead without enough reuse to justify it.</p><p><strong>Why does local NVMe matter for streaming jobs?</strong></p><p>Because shuffle, spill, and write-heavy micro-batches are sensitive to disk performance. Faster local storage can reduce batch-time variability and improve throughput for join-heavy workloads.</p><p><strong>Should I always change </strong><code>spark.sql.shuffle.partitions</code><strong> manually?</strong></p><p>No, almost never unless you absolutely can live without them. Auto-optimized settings may be sufficient. </p><p><strong>What does &#8220;success&#8221; mean for a latency-bound streaming job?</strong></p><p>Success is not just that the query keeps running. Success means the pipeline consistently finishes each micro-batch within the target latency window and prevents backlog from growing during spikes.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Stop Hand-Building Your Streaming Ops Layer]]></title><description><![CDATA[Structured Streaming gives you the engine. SDP gives you the pit crew. Here's why I reach for it first &#8212; and the two times I don't.]]></description><link>https://www.databricksters.com/p/stop-hand-building-your-streaming</link><guid isPermaLink="false">https://www.databricksters.com/p/stop-hand-building-your-streaming</guid><dc:creator><![CDATA[Canadian Data Guy]]></dc:creator><pubDate>Mon, 06 Jul 2026 15:02:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zRKU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Structured Streaming gives you raw power. Spark Declarative Pipelines (SDP) gives you that same power with a production-grade operations layer built on top, right out of the box.</p><p>I&#8217;ve spent the last 5 years designing and running thousands of streaming jobs. Here&#8217;s how I actually see the tradeoff.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zRKU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zRKU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zRKU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zRKU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zRKU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zRKU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg" width="1456" height="1092" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3107212,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.databricksters.com/i/204534019?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zRKU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zRKU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zRKU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zRKU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eeb6898-1d7c-47b5-9b2a-e482a7789077_2390x1792.jpeg 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>1. Zero-Downtime Runtime Upgrades</span></h2><p><span>This is one of the most under-appreciated advantages of SDP. With a classic Structured Streaming job on regular compute, a runtime upgrade looks like this:</span></p><ol><li><p><strong><span>Stop the job</span></strong><span> (downtime begins)</span></p></li><li><p><strong><span>Update the runtime version</span></strong><span> on the cluster</span></p></li><li><p><strong><span>Restart the job</span></strong></p></li><li><p><strong><span>Wait</span></strong><span> for startup and checkpoint recovery</span></p></li></ol><p><span>Every up</span>grade becomes a scheduled maintenance window. Because of this, teams constantly defer them, accumulate technical <span>debt, and eventually face a forced migration under pressure.</span></p><blockquote><p><strong><span>With SDP, your pipelines are versionless.</span></strong><span> Databricks manages the runtime lifecycle for you. New compute spins up in the background, your pipeline migrates over automatically, and there&#8217;s no restart sequence to plan aro</span>und. If you are running a few dozen streaming pipelines, this alone eliminates an entire category of operational toil.</p></blockquote><div><hr></div><h2>2. Infrastructure You Don&#8217;t Babysit</h2><ul><li><p><strong>Serverless SDP:</strong> There is no cluster to configure. SDP auto-scales, provisions, and tears down compute automatically. You write the pipeline logic; the platform handles the rest. No sizing debates, no idle costs between micro-batches, and SLA-backed startup times.</p></li><li><p><strong>Classic SDP:</strong> If you aren&#8217;t on serverless, you still get <strong>Enhanced Autoscaling</strong>. This feature is purpose-built for streaming, right-sizing your clusters based on actual backlog and throughput rather than rough guesses.</p></li></ul><div><hr></div><h2>3. Rewind and Recover, Instead of Rebuild</h2><p>Production pipelines break. Bad data ships, an unexpected code change causes corruption, or a bug slips through code review. On raw Structured Streaming, recovery usually means manually resetting checkpoints or reprocessing everything from scratch.</p><p><strong>SDP&#8217;s Rewind API changes that story.</strong> With a single operation, you can restore a pipeline&#8212;including table data, source offsets, and operator state together&#8212;to a clean point in time, apply your fix, and resume.</p><ul><li><p>No checkpoint deletion required.</p></li><li><p>No full refreshes needed.</p></li><li><p>The question shifts from <em>&#8220;How long until we&#8217;re caught up?&#8221;</em> to <em>&#8220;Rewind, fix, resume.&#8221;</em></p></li></ul><p><em>(Note: This is currently available on request&#8212;reach out via Databricks to get access.)</em></p><div><hr></div><h2>4. Quality, Dependencies, and Schema: Native, Not Bolted On</h2><p>On raw Structured Streaming, you have to build and maintain operational guardrails yourself. On SDP, they come baked into the platform:</p><ul><li><p><strong>Data Quality:</strong> Declare <code>@dp.expect</code> rules directly in your pipeline code. Choose whether to <code>warn</code>, <code>drop</code>, or <code>fail</code> per rule. Violations are tracked and visualized in the pipeline UI rather than being silently dropped.</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;a714b45f-3cb0-440c-9649-0e0fd524030f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">@dp.expect("valid_order_id", "order_id IS NOT NULL")
@dp.expect_or_drop("positive_amount", "amount &gt; 0")
@dp.expect_or_fail("no_duplicates", "COUNT(*) = COUNT(DISTINCT order_id)")
def silver_orders():
    return dlt.read_stream("bronze_orders")
</code></pre></div><ul><li><p><strong>Multi-Table Dependencies:</strong> SDP automatically resolves the full DAG (Directed Acyclic Graph) on its own. You don&#8217;t need an external orchestrator for pipeline-internal chains.</p></li><li><p><strong>Schema Evolution:</strong> Unexpected or changed fields automatically route to a <code>rescue</code> column instead of crashing your production run.</p></li><li><p><strong>CDC (Change Data Capture):</strong> The <code>APPLY CHANGES INTO</code> syntax gives you SCD Type 1/2 out of the box&#8212;no hand-written, complex <code>MERGE</code> logic required.</p></li></ul><p>Add a visual pipeline graph UI, a queryable event log, and row-level lineage through Unity Catalog.</p><div><hr></div><h2>5. When Structured Streaming Is Still the Right Call</h2><p>While you should default to SDP, two situations are worth reconsidering:</p><ul><li><p><strong>Library Compatibility:</strong> Your pipeline depends on third-party or custom libraries that don&#8217;t play well with serverless compute environments. Classic compute gives you full control over the environment.</p></li><li><p><strong>An Actual SDP Ceiling:</strong> You&#8217;ve hit a hard, documented limitation for your specific use case, rather than a hypothetical one.</p></li></ul><p>For everything else, choose <span>SDP first.</span></p><h2>The Bottom Line</h2><p>SDP doesn&#8217;t replace Structured Streaming&#8212;it removes the operational tax most teams end up paying anyway by building this layer poorly by hand. You ship faster, operate with less toil, and keep raw Structured Streaming as an escape hatch for the rare cases that genuinely need lower-level engine control.</p><p>If you are evaluating SDP for the first time or weighing whether to migrate existing Structured Streaming pipelines, talk to your Databricks account team. They can walk through your specific architecture and help you identify where the operational wins are real versus theoretical.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Wk_h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Wk_h!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png 424w, https://substackcdn.com/image/fetch/$s_!Wk_h!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png 848w, https://substackcdn.com/image/fetch/$s_!Wk_h!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png 1272w, https://substackcdn.com/image/fetch/$s_!Wk_h!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!Wk_h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png" width="663" height="456" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:456,&quot;width&quot;:663,&quot;resizeWidth&quot;:663,&quot;bytes&quot;:63120,&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://www.databricksters.com/i/204534019?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png&quot;,&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_!Wk_h!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png 424w, https://substackcdn.com/image/fetch/$s_!Wk_h!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png 848w, https://substackcdn.com/image/fetch/$s_!Wk_h!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png 1272w, https://substackcdn.com/image/fetch/$s_!Wk_h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa52b2d2d-8e28-48bd-8c8e-6bed1261dc83_663x456.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2>FAQ</h2><h5>What is SDP?</h5><p>SDP stands for <strong>Spark Declarative Pipelines</strong>. It provides a declarative way to build data pipelines with built-in operations, quality enforcement, dependency management, and observability.</p><h5>How is SDP different from Structured Streaming?</h5><p>Structured Streaming is the underlying core engine. SDP builds <em>on top</em> of it, adding production-focused capabilities like managed upgrades, pipeline orchestration, quality rules, and native monitoring.</p><h5>Is SDP only useful for large teams?</h5><p>No. Smaller teams often benefit even more because SDP removes the heavy operational lifting that smaller engineering teams don&#8217;t have the time or staffing to manage manually.</p><h5>Is SDP only useful for streaming workloads?</h5><p>No. While it is exceptionally strong for streaming and incremental pipelines, it also works seamlessly for multi-hop data pipelines that combine both batch and streaming patterns.</p><h5>Does SDP replace orchestration tools?</h5><p>For dependencies <em>inside</em> a single pipeline, often yes. SDP manages execution order and downstream dependencies automatically, reducing the need for an external orchestrator within that specific pipeline boundary.</p>]]></content:encoded></item><item><title><![CDATA[Why Your information_schema Queries Are Killing the Metastore]]></title><description><![CDATA[And the 3 Functions to Use Instead]]></description><link>https://www.databricksters.com/p/why-your-information_schema-queries</link><guid isPermaLink="false">https://www.databricksters.com/p/why-your-information_schema-queries</guid><dc:creator><![CDATA[Canadian Data Guy]]></dc:creator><pubDate>Tue, 30 Jun 2026 15:01:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!isGN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometimes engineers want to check if a table exists before writing or merging data. To do this, they often loop through a list of tables and query <code>information_schema.tables</code>.</p><div><hr></div><h2>&#10060; The Anti-Pattern: Full Catalog Scans in a Loop</h2><p>Here is the code that can causes pipelines to hit metastore rate limits or fail entirely:</p><pre><code><code># This forces a full scan of the entire metastore catalog on every execution
spark.read.table("catalog.information_schema.tables") \
    .filter((col("table_schema") == my_schema) &amp; (col("table_name") == my_table))
</code></code></pre><h3><span>The Technical Bottlene</span>ck</h3><p>The <code>information_schema</code> is built for wide-scope catalog introspection&#8212;such as <span>auditing, broad governance mapping, or checking metadata across an entire environment at once.</span></p><p><span>When you chain a </span><code>.filter()</code><span> in Spark </span><strong><span>after</span></strong><span> loading </span><code>information_schema.tables</code><span>, there is </span><strong><span>zero predicate pushdown</span></strong><span> to the underlying metastore database. The metast</span>ore is forced <span>to enumerate and return </span><em><span>every single object</span></em><span> inside that catalog to the Spark engine, leaving Spark to do the actual row filtering client-side in memory.</span></p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!isGN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!isGN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg 424w, https://substackcdn.com/image/fetch/$s_!isGN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg 848w, https://substackcdn.com/image/fetch/$s_!isGN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg 1272w, https://substackcdn.com/image/fetch/$s_!isGN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!isGN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg" width="1456" height="863" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:863,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5561,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/svg+xml&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.databricksters.com/i/203766763?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!isGN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg 424w, https://substackcdn.com/image/fetch/$s_!isGN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg 848w, https://substackcdn.com/image/fetch/$s_!isGN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg 1272w, https://substackcdn.com/image/fetch/$s_!isGN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F555d1579-2cf7-46e9-8ad7-2e34682e62ef_1080x640.svg 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2><span>The Solution: Use True Point-Lookup APIs</span></h2><p><span>If you only need to verify the existence of a single table, you must bypass catalog-wide views and target a single metadata record directly.</span></p><h3><span>Option 1: Programmatic Check via </span><code>tableExists()</code><span> (Recommended)</span></h3><p><span>For pure automation pipelines, the native Spark catalog API handles this efficiently without compiling a heavy relational query pl</span>an. It hits a single metadata record instantly.</p><pre><code><code># Target a single metadata record directly
exists = spark.catalog.tableExists("catalog.schema.table")
</code></code></pre><h3><span>Option 2: SQL Scoped Check via </span><code>SHOW TABLES</code></h3><p><span>If you are working strictly within raw SQL strings, limit your query scope using an explicit schema filter an</span>d a wildcard match rather than querying the entire catalog.</p><pre><code><code>-- Fast, schema-scoped point lookup
SHOW TABLES IN catalog.schema LIKE 'table_name';
</code></code></pre><h3>Option 3: Structural Validation via <code>DESCRIBE TABLE</code></h3><p>Often, you don&#8217;t just need to know if a table exists&#8212;you need to ensure its current column layout matches what your engine expects. You can leverage structural evaluation enclosed in an exception block.</p><pre><code><code>from pyspark.sql.utils import AnalysisException

# Validates existence and structure simultaneously 
try:
    spark.sql("DESCRIBE TABLE catalog.schema.table")
    exists = True
except AnalysisException:
    # Catch AnalysisException specifically. A bare 'except' turns 
    # permission errors and transient timeouts into false "table missing" signals.
    exists = False
</code></code></pre><h2>When Do You Actually Need <code>information_schema</code>?</h2><p>If your objective is broad discovery&#8212;like mapping end-to-end data lineage across the entire workspace&#8212;using <code>information_schema</code> is completely appropriate. However, you should follow this single operational rule: <strong>Read it exactly once at the top of the job, cache it, and filter from memory.</strong></p><pre><code><code># Execute exactly one metastore scan total
all_tables = spark.read.table("catalog.information_schema.tables").cache()

# Filter from the cached memory layer as many times as needed
bronze_tables = all_tables.filter(col("table_schema") == "bronze")
silver_tables = all_tables.filter(col("table_schema") == "silver")
</code></code></pre><div><hr></div><h2>The Ultimate Fix: Cut the Check Entirely</h2><p>Pre-flight existence checks usually exist because a pipeline doesn&#8217;t know whether to create a new table or append to an existing one. If you are using Delta Lake, Delta already solves this for you.</p><p>Using <code>.mode("append")</code> will automatically create the table on the first write, and seamlessly append to it on every run after:</p><pre><code><code>df.write \
  .format("delta") \
  .mode("append") \
  .saveAsTable("catalog.schema.target_table")
</code></code></pre><p>SQL pipelines get the exact same guarantee from a single DDL line at setup:</p><pre><code><code>CREATE TABLE IF NOT EXISTS catalog.schema.target_table (
    id INT,
    data STRING,
    event_time TIMESTAMP
) USING DELTA;

INSERT INTO catalog.schema.target_table SELECT * FROM staging_view;
</code></code></pre><blockquote><p><strong>The Bottom Line:</strong> When you see an existence check before a write in a code review, ask what it&#8217;s actually guarding against. Usually, there&#8217;s no good answer&#8212;and removing it will save your metastore from a world of hurt.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[The Mbappé Effect: Deploying French Stuff on Serverless GPUs]]></title><description><![CDATA[Proprietary foundation models are great for a wide array of use cases, but many companies have legitimate need for an OSS model.]]></description><link>https://www.databricksters.com/p/the-mbappe-effect-deploying-french</link><guid isPermaLink="false">https://www.databricksters.com/p/the-mbappe-effect-deploying-french</guid><dc:creator><![CDATA[Austin]]></dc:creator><pubDate>Tue, 30 Jun 2026 14:03:24 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/553c1a8a-b1a7-41bd-bfc1-1050d84f017c_1600x922.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span>Proprietary foundation models are great for a wide array of use cases, but many companies have legitimate need for an OSS model. Sometimes it&#8217;s a security concern, other times it&#8217;s about tuning and optimizing for task specific performance, and sometimes it just comes down to cost or latency SLAs. Whatever your reason, you want to serve an LLM to custom GPU model serving, and I&#8217;m here to help. </span></p><p><span>And before you accuse me of writing clickbait titles that have little to do with the content of the blog (I would never do that), I assure you that Mbapp&#233; will be mentioned no fewer than 4 times.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8-fu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8-fu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg 424w, https://substackcdn.com/image/fetch/$s_!8-fu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg 848w, https://substackcdn.com/image/fetch/$s_!8-fu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!8-fu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8-fu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg" width="1456" height="1092" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:381698,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.databricksters.com/i/204202223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8-fu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg 424w, https://substackcdn.com/image/fetch/$s_!8-fu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg 848w, https://substackcdn.com/image/fetch/$s_!8-fu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!8-fu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827e4bcd-efa9-446d-96e4-90af5df3159c_1600x1200.jpeg 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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">&lt;INSERT CLICKBAIT IMAGE HERE&gt;</figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.databricksters.com/subscribe?"><span>Subscribe now</span></a></p><p>The landscape of LLMs evolves so rapidly that it&#8217;s nearly impossible to keep up with every model, let alone every dependency change that may be required by them. <span>With AI Runtime and Custom GPU Model Serving, it&#8217;s trivially easy to serve the latest OSS models, because we can edit the versions at the outset, just as we would any other notebook. </span></p><p>In honor of their World Cup victory, let&#8217;s use a Mistral model: <code>Devstral-Small-2-24B-Instruct-2512</code><span>, a great mid-sized model for SWE tasks that deploys easily on Databricks custom GPU model serving.</span></p><p><span>Mbapp&#233;.</span></p><p><span>Most of these package versions are shipped natively with AI Runtime v5, but just to underscore my above point, I&#8217;ll set them here as well:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;ca3491a1-aa02-463e-b565-03e6e2270fc4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">%pip install databricks-sdk==0.102.0 vllm==0.13.0 transformers==4.57.6 mistral_common openai==2.17.0 opencv-python-headless==4.12.* mlflow==3.12.0 hf_transfer==0.1.9
%restart_python</code></pre></div><p><span>We&#8217;ll now set the wd to local disk and configure the endpoint. We set </span><code>SKIP_LOCAL_SERVE_TEST=True</code><span> unless we want to load the whole thing into RAM here in the serverless notebook. That would work on a single node H100, but not on an A10. We can also set the max model length here if we don&#8217;t need to service long prompts and want to conserve VRAM during serving. We&#8217;ll also set the location in UC we want to save this to, the endpoint name, and the sizing here as well.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;759bb310-d93e-49dd-88b3-686b77d49775&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Set working directory to local disk (/Workspace doesn't support large files).
import os, tempfile
workdir = tempfile.mkdtemp()
os.chdir(workdir)
os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"
# Hugging Face model to download.
MODEL_REPO_ID = "mistralai/Devstral-Small-2-24B-Instruct-2512"
ARTIFACTS_PATH = "devstral"        # Local dir the weights download to
SERVED_MODEL_NAME = "devstral"     # Name vLLM exposes the model under.

# Devstral 2 Small FP8 (~24 GB) won't fit an A10
SKIP_LOCAL_SERVE_TEST = True
MAX_MODEL_LEN = 24576
GPU_MEMORY_UTILIZATION = 0.90

# Allowlisted ports for Serverless GPU notebooks are 3000-3999. Model Serving requires 8080.
LOCAL_PORT = 3080
SERVING_PORT = 8080

# Unity Catalog destination
UC_MODEL_NAME = "catalog.schema.devstral_small_2_24b_fp8"

# Serving endpoint configuration.
ENDPOINT_NAME = "austin-devstral-24b-h100"
WORKLOAD_SIZE = "Small"
SCALE_TO_ZERO_ENABLED = False   # GPU_XLARGE/H100 does NOT support scale-to-zero in Beta

from databricks.sdk.service.serving import ServingModelWorkloadType
WORKLOAD_TYPE = ServingModelWorkloadType.GPU_XLARGE</code></pre></div><p><span>Mbapp&#233;.</span></p><p><span>Now we can download the model:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;ed2947e9-50a4-43d9-abb8-00f25d4aaab3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from huggingface_hub import snapshot_download

snapshot_download(
   repo_id=MODEL_REPO_ID,
   local_dir=ARTIFACTS_PATH,
)</code></pre></div><p>And log it to our custom entry point and print the model URI in case we want to reference that later. This will take a few minutes. Note that we do NOT download from HF in the serving container.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;1b9473c2-fef1-4d95-a850-88d44fc59d09&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import mlflow
from mlflow.pyfunc.model import ChatModel, ChatCompletionResponse

# Required placeholder. Serving runs the entrypoint, not python_model.predict.
class LLMModel(ChatModel):
   def predict(self, context, messages, params):
       return ChatCompletionResponse.from_dict({"choices": []})

mlflow.set_registry_uri("databricks-uc")

model_info = mlflow.pyfunc.log_model(
   name=SERVED_MODEL_NAME,
   python_model=LLMModel(),
   artifacts={
       "model_dir": ARTIFACTS_PATH,
   },
   metadata={
       "task": "llm/v1/chat",
       "entrypoint": entrypoint(SERVING_PORT),
   },
   extra_pip_requirements=[
       "mlflow==3.12.0",
   ],
)
model_info.model_uri</code></pre></div><p>Now we register the model. Note that <code>env_pack="databricks_model_serving"</code> packages the env and the bundled weights for Serverless Optimized Deployments. This step will take like 15 minutes.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;ca33a911-3c75-4826-8573-eaa5698fdbe7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">model_version = mlflow.register_model(model_info.model_uri, UC_MODEL_NAME, env_pack="databricks_model_serving")
print(f"Registered {UC_MODEL_NAME} version {model_version.version}")</code></pre></div><p><span>Mbapp&#233;.</span></p><p><span>Our model is now ready for deployment. This cell provisions its own </span><code>GPU_XLARGE</code><span> (i.e. H100) serving endpoint independent of this A10 notebook.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;63ef1a07-97c9-4ad6-b140-3f3f386bf057&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from databricks.sdk import WorkspaceClient
from datetime import timedelta
from databricks.sdk.service.serving import EndpointCoreConfigInput, ServedEntityInput

config = EndpointCoreConfigInput(
   name=ENDPOINT_NAME,
   served_entities=[
       ServedEntityInput(
           entity_name=UC_MODEL_NAME,
           entity_version=str(model_version.version),
           workload_type=WORKLOAD_TYPE,
           workload_size=WORKLOAD_SIZE,
           scale_to_zero_enabled=SCALE_TO_ZERO_ENABLED,
       )
   ]
)

w = WorkspaceClient()
w.serving_endpoints.create_and_wait(name=ENDPOINT_NAME, config=config, timeout=timedelta(minutes=45))</code></pre></div><p>And you&#8217;ve done it. With that, all that&#8217;s left to do is query the endpoint to confirm it&#8217;s working.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;2837d8f5-bf37-4c74-817a-ca5341edb74f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from databricks.sdk import WorkspaceClient
from databricks.sdk.service.serving import ChatMessage, ChatMessageRole

w = WorkspaceClient()
resp = w.serving_endpoints.query(
   name=ENDPOINT_NAME,
   messages=[ChatMessage(role=ChatMessageRole.USER, content="Hi, will Mbapp&#233; lead France to another World Cup victory? And by extension make Mistral the world favorite OSS LLM for SWE tasks?")],
)
print(resp.choices[0].message.content)</code></pre></div><p><span>Happy coding,</span></p><p><span>-Austin</span></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><item><title><![CDATA[Stop Guessing AI Agent Integrations: The Databricks Decision Tree]]></title><description><![CDATA[Discover essential patterns for secure, scalable AI agent integration with Databricks, covering OBO, token federation, and multi-agent strategies. Get artifact checklists and CLI commands.]]></description><link>https://www.databricksters.com/p/stop-guessing-ai-agent-integrations</link><guid isPermaLink="false">https://www.databricksters.com/p/stop-guessing-ai-agent-integrations</guid><dc:creator><![CDATA[Aravind Nallan]]></dc:creator><pubDate>Wed, 24 Jun 2026 16:28:29 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f7c9d4c1-f15e-4256-8b42-c56e67fc83c7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><span>The Problem</span></h2><p><span>Business users do most of their work outside of a data platform: sales reps draft proposals in Outlook, operations leads run their day in Teams, finance models everything in Excel.Yet the answers to most of their questions sit in Databricks: pipeline metrics, supply chain status, support SLAs, financial actuals, etc.</span></p><p><span>That gap between &#8220;where data lives&#8221; and &#8220;where people work&#8221; used to be bridged by reports and dashboards. Now the bridge is AI agents: assistants embedded in Teams, Outlook, Excel, Copilot Hub, Microsoft Foundry projects, or an in-house chat app. The user asks a question in their flow of work. The agent calls back to Databricks. The answer returns with proper governance applied.</span></p><p><span>The pattern is simple in theory. In practice, the integration involves a few interlocking decisions: which Databricks resources to wire up, how authentication flows, whether the agent supports MCP or only REST, whether users and Databricks share a Microsoft Entra tenant, and what governance controls you need. Get them wrong and you lose all the benefits of governance inherent to Databricks.</span></p><p><span>This post walks you through a decision tree to get to the right architecture, with the exact set of artifacts to share between the Databricks side and the agent side at the end.</span></p><h2><span>Architecture Components</span></h2><p><span>Before walking the tree, here are some quick definitions of the different Databricks functions we will be dealing with.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U0NG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U0NG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png 424w, https://substackcdn.com/image/fetch/$s_!U0NG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png 848w, https://substackcdn.com/image/fetch/$s_!U0NG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png 1272w, https://substackcdn.com/image/fetch/$s_!U0NG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U0NG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png" width="1456" height="803" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:803,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:197754,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.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_!U0NG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png 424w, https://substackcdn.com/image/fetch/$s_!U0NG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png 848w, https://substackcdn.com/image/fetch/$s_!U0NG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.png 1272w, https://substackcdn.com/image/fetch/$s_!U0NG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa903ad22-7ec2-4d2f-9a3f-fbe71ebeac09_1574x868.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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2><span>Question 1: Which Databricks Resources Need to Be Connected?</span></h2><p><span>Databricks provides four managed MCP servers out of the box. Each connects a different resource type with its own URL pattern and OAuth scope.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!b00I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b00I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png 424w, https://substackcdn.com/image/fetch/$s_!b00I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png 848w, https://substackcdn.com/image/fetch/$s_!b00I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png 1272w, https://substackcdn.com/image/fetch/$s_!b00I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!b00I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png" width="1456" height="772" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:772,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:200456,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.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_!b00I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png 424w, https://substackcdn.com/image/fetch/$s_!b00I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png 848w, https://substackcdn.com/image/fetch/$s_!b00I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png 1272w, https://substackcdn.com/image/fetch/$s_!b00I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32198225-5e6b-490f-86c0-e670a148a3ee_1572x834.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>All four are governed by Unity Catalog permissions and visible in </span><strong><span>AI Gateway &gt; MCPs</span></strong><span>.</span></p><p><strong><span>Note on Vector Search:</span></strong><span> The managed MCP server works directly with indexes that use Databricks-managed embeddings; you send plain text and get plain text results. If you use self-managed embeddings, wrap the index in a Model Serving endpoint (for example, via VectorSearchRetrieverTool in a deployed agent) or a UC Function, and call that from the agent instead.</span></p><h2><span>Question 2: How Many Databricks Endpoints, and Does the Agent Support Multiple Tool Endpoints?</span></h2><p><span>This determines whether you connect a single URL or several.</span></p><h3><span>Path A: Single resource</span></h3><p><span>Connect one managed MCP URL directly. Simplest path: no orchestration needed.</span></p><h3><span>Path B: Multiple resources, agent supports multiple endpoints</span></h3><p><span>Create one OAuth application with combined scopes and hand the agent team all the MCP URLs. The same OAuth token works across all of them.</span></p><p><span>OAuth app scopes (combine as needed):</span></p><p><code>vector-search genie unity-catalog sql offline_access</code></p><p><span>Or use all-apis for blanket access (less restrictive).</span></p><p><span>One OAuth flow, one token, multiple MCP servers. The user authenticates once and the token carries the correct scopes for every resource.</span></p><h3><span>Path C: Multiple resources, agent supports only one endpoint</span></h3><p><span>Two options.</span></p><p><strong><span>Option 1: Supervisor Agent (recommended for most cases)</span></strong></p><p><span>Register each Databricks resource as a sub-agent of an AgentBricks Multi-Agent Supervisor Agent. The external agent connects to one Model Serving endpoint.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PsNU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PsNU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png 424w, https://substackcdn.com/image/fetch/$s_!PsNU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png 848w, https://substackcdn.com/image/fetch/$s_!PsNU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png 1272w, https://substackcdn.com/image/fetch/$s_!PsNU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!PsNU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png" width="1456" height="459" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f952ed30-1dea-435b-a141-055fb3879532_1574x496.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:459,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:106124,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.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_!PsNU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png 424w, https://substackcdn.com/image/fetch/$s_!PsNU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png 848w, https://substackcdn.com/image/fetch/$s_!PsNU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png 1272w, https://substackcdn.com/image/fetch/$s_!PsNU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff952ed30-1dea-435b-a141-055fb3879532_1574x496.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>The external agent connects to a single REST endpoint: POST https://&lt;workspace&gt;/serving-endpoints/&lt;supervisor&gt;/invocations. The supervisor&#8217;s LLM handles routing: it reads the user&#8217;s query and delegates to the right sub-agent based on descriptions you provide.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pi4z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pi4z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png 424w, https://substackcdn.com/image/fetch/$s_!pi4z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png 848w, https://substackcdn.com/image/fetch/$s_!pi4z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png 1272w, https://substackcdn.com/image/fetch/$s_!pi4z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!pi4z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png" width="1456" height="496" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:496,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:117348,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.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_!pi4z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png 424w, https://substackcdn.com/image/fetch/$s_!pi4z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png 848w, https://substackcdn.com/image/fetch/$s_!pi4z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png 1272w, https://substackcdn.com/image/fetch/$s_!pi4z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a973314-71d2-4f87-b7e0-ce278a3cfeed_1572x536.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><strong><span>Option 2: Custom MCP Server (Databricks App)</span></strong></p><p><span>Deploy a Databricks App that proxies to multiple managed MCP servers behind a single URL: https://&lt;app-url&gt;/mcp.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yCVV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yCVV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png 424w, https://substackcdn.com/image/fetch/$s_!yCVV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png 848w, https://substackcdn.com/image/fetch/$s_!yCVV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png 1272w, https://substackcdn.com/image/fetch/$s_!yCVV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!yCVV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png" width="1456" height="380" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:380,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:78296,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.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_!yCVV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png 424w, https://substackcdn.com/image/fetch/$s_!yCVV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png 848w, https://substackcdn.com/image/fetch/$s_!yCVV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png 1272w, https://substackcdn.com/image/fetch/$s_!yCVV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F544cf676-009b-4c63-9499-5122f0e119b0_1570x410.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><strong><span>Decision rule:</span></strong><span> Minimal code with LLM-driven routing goes to Supervisor Agent. Lightweight deterministic proxying goes to Custom MCP App.</span></p><h2><span>Question 3: What Authentication Model Is Required?</span></h2><p><span>This is the most consequential decision. It determines how identity flows from the external agent to Databricks.</span></p><h3><span>The key question: does the end user&#8217;s identity need to flow through to Databricks?</span></h3><p><strong><span>Yes: On-Behalf-Of (OBO) Authentication</span></strong></p><p><span>The external agent authenticates on behalf of each individual user. Databricks receives the user&#8217;s identity, and Unity Catalog permissions are enforced per-user.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ABg0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ABg0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png 424w, https://substackcdn.com/image/fetch/$s_!ABg0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png 848w, https://substackcdn.com/image/fetch/$s_!ABg0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png 1272w, https://substackcdn.com/image/fetch/$s_!ABg0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!ABg0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png" width="1456" height="384" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:384,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:109734,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.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_!ABg0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png 424w, https://substackcdn.com/image/fetch/$s_!ABg0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png 848w, https://substackcdn.com/image/fetch/$s_!ABg0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png 1272w, https://substackcdn.com/image/fetch/$s_!ABg0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47f1b1f-0ddc-454a-b829-c032209c6789_1570x414.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>OBO-supported resources on Model Serving:</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Bpmf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Bpmf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png 424w, https://substackcdn.com/image/fetch/$s_!Bpmf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png 848w, https://substackcdn.com/image/fetch/$s_!Bpmf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png 1272w, https://substackcdn.com/image/fetch/$s_!Bpmf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!Bpmf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png" width="1456" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:675,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:131935,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.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_!Bpmf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png 424w, https://substackcdn.com/image/fetch/$s_!Bpmf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png 848w, https://substackcdn.com/image/fetch/$s_!Bpmf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png 1272w, https://substackcdn.com/image/fetch/$s_!Bpmf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb205e14-7134-4d69-83cf-66cb84e457bd_1570x728.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>If you need OBO access to resources outside this list (e.g., UC Volumes), deploy on Databricks Apps instead of Model Serving. Apps support additional OAuth scopes.</span></p><p><strong><span>No: Service Principal Authentication</span></strong></p><p><span>The external agent authenticates as a single identity (service principal). All queries run under that SP&#8217;s permissions.</span></p><div class="captioned-image-container"><figure><div class="image-link image2" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7tQh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png 424w, https://substackcdn.com/image/fetch/$s_!7tQh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png 848w, https://substackcdn.com/image/fetch/$s_!7tQh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png 1272w, https://substackcdn.com/image/fetch/$s_!7tQh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!7tQh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png" width="1456" height="349" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:349,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:86351,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.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_!7tQh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png 424w, https://substackcdn.com/image/fetch/$s_!7tQh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png 848w, https://substackcdn.com/image/fetch/$s_!7tQh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png 1272w, https://substackcdn.com/image/fetch/$s_!7tQh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ab886a-a30b-4787-99c5-a68a36a15c7a_1570x376.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div></div></div></div></figure></div><h3><span>Authentication Method Comparison</span></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Qgja!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Qgja!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png 424w, https://substackcdn.com/image/fetch/$s_!Qgja!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png 848w, https://substackcdn.com/image/fetch/$s_!Qgja!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png 1272w, https://substackcdn.com/image/fetch/$s_!Qgja!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!Qgja!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png" width="1456" height="494" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:494,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:110513,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.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_!Qgja!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png 424w, https://substackcdn.com/image/fetch/$s_!Qgja!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png 848w, https://substackcdn.com/image/fetch/$s_!Qgja!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png 1272w, https://substackcdn.com/image/fetch/$s_!Qgja!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d9f90-9aa0-4941-8d15-a1cb73c1dd81_1568x532.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>The question to ask the data security team: do different users need different access levels to the data behind these resources, or is a shared service account acceptable?</span></p><h2><span>Question 4: Are Users and Databricks in the Same Azure Tenant?</span></h2><p><span>This question only matters if you chose OBO or token federation in Q3. With a simple PAT or client credentials inside the same org, skip to Q5.</span></p><h3><span>Same tenant</span></h3><p><span>Standard OAuth flows work directly:</span></p><ul><li><p><span>Users already exist in Databricks via SCIM sync from the same Entra ID</span></p></li><li><p><span>OAuth authorization code flow authenticates users against the same tenant</span></p></li><li><p><span>No federation policy needed</span></p></li></ul><h3><span>Different tenants (cross-tenant)</span></h3><p><span>Common in enterprise scenarios: users live in Tenant A (corporate Entra ID), but Databricks is associated with Tenant B.</span></p><p><strong><span>Path A: Per-user OBO (users in external tenant need individual identity)</span></strong></p><ol><li><p><span>Create an account-wide federation policy:</span></p><ul><li><p><strong><span>Issuer:</span></strong><span> https://login.microsoftonline.com/&lt;external-tenant-id&gt;/v2.0</span></p></li><li><p><strong><span>Subject claim:</span></strong><span> sub or oid (to match users by their Entra ID object ID)</span></p></li><li><p><strong><span>Audience:</span></strong><span> Databricks account ID or custom value</span></p></li></ul></li><li><p><span>Provision users in Databricks. Federation validates identity but does NOT create accounts. Use SCIM sync from the external tenant, or provision manually.</span></p></li><li><p><span>Each user&#8217;s Entra ID token is exchanged for a Databricks token carrying their identity.</span></p></li><li><p><span>Unity Catalog permissions apply per-user.</span></p></li></ol><p><strong><span>Path B: Service Principal (automated agent, no per-user identity)</span></strong></p><ol><li><p><span>Create a Databricks service principal in your Databricks account.</span></p></li><li><p><span>Create a service principal federation policy:</span></p><ul><li><p><strong><span>Issuer:</span></strong><span> https://login.microsoftonline.com/&lt;external-tenant-id&gt;/v2.0</span></p></li><li><p><strong><span>Subject:</span></strong><span> The Entra ID app/service principal&#8217;s sub or oid claim</span></p></li><li><p><strong><span>Audience:</span></strong><span> Databricks account ID</span></p></li></ul></li><li><p><span>The external agent obtains a JWT from its own Entra ID tenant.</span></p></li><li><p><span>Exchanges it via:</span></p></li></ol><p><span>   </span><code>POST https://&lt;workspace&gt;/oidc/v1/token</code></p><p><code>   grant_type=urn:ietf:params:oauth:grant-type:token-exchange</code></p><p><code>   subject_token=&lt;entra-id-jwt&gt;</code></p><p><code>   subject_token_type=urn:ietf:params:oauth:token-type:jwt</code></p><ol><li><p><span>Zero Databricks secrets cross the tenant boundary.</span></p></li></ol><h3><span>Cross-tenant requirements checklist</span></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Z-zu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Z-zu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png 424w, https://substackcdn.com/image/fetch/$s_!Z-zu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png 848w, https://substackcdn.com/image/fetch/$s_!Z-zu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png 1272w, https://substackcdn.com/image/fetch/$s_!Z-zu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!Z-zu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png" width="1456" height="609" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cb723616-c024-4200-8cfc-e951d18c538c_1578x660.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:609,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:147124,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.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_!Z-zu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png 424w, https://substackcdn.com/image/fetch/$s_!Z-zu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png 848w, https://substackcdn.com/image/fetch/$s_!Z-zu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png 1272w, https://substackcdn.com/image/fetch/$s_!Z-zu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb723616-c024-4200-8cfc-e951d18c538c_1578x660.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2><span>Question 5: What Governance Controls Are Needed?</span></h2><p><span>AI Gateway is the enterprise control plane that sits between MCP servers and external agents. It provides the operational controls needed in production.</span></p><h3><span>AI Gateway Capabilities for MCP</span></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1Rld!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1Rld!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png 424w, https://substackcdn.com/image/fetch/$s_!1Rld!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png 848w, https://substackcdn.com/image/fetch/$s_!1Rld!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png 1272w, https://substackcdn.com/image/fetch/$s_!1Rld!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!1Rld!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png" width="1456" height="1112" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1112,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:278599,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.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_!1Rld!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png 424w, https://substackcdn.com/image/fetch/$s_!1Rld!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png 848w, https://substackcdn.com/image/fetch/$s_!1Rld!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png 1272w, https://substackcdn.com/image/fetch/$s_!1Rld!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0eb8e9-2600-40c0-b0d3-3ad8d2bfd648_1574x1202.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>Find it in: Workspace sidebar &gt; </span><strong><span>AI Gateway</span></strong><span> &gt; </span><strong><span>MCPs</span></strong><span>.</span></p><h3><span>Recommended governance setup</span></h3><ol><li><p><strong><span>Rate limiting</span></strong><span>: set conservative limits initially, increase based on observed usage.</span></p></li><li><p><strong><span>Usage tracking</span></strong><span>: enable from day one to establish a baseline.</span></p></li><li><p><strong><span>Audit logging</span></strong><span>: required for any integration where per-user identity flows through (OBO).</span></p></li><li><p><strong><span>IP allowlisting</span></strong><span>: restrict MCP server access to known external agent IPs.</span></p></li><li><p><strong><span>Scope-limited OAuth</span></strong><span>: use granular scopes (vector-search, genie) instead of all-apis for production.</span></p></li></ol><h2><span>Question 6: What Information Needs to Be Exchanged Between Teams?</span></h2><p><span>Setting up the integration requires a handshake between the Databricks team and the external agent team. Here is exactly what each side provides.</span></p><h3><span>From the Databricks side</span></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!niTp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!niTp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png 424w, https://substackcdn.com/image/fetch/$s_!niTp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png 848w, https://substackcdn.com/image/fetch/$s_!niTp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png 1272w, https://substackcdn.com/image/fetch/$s_!niTp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!niTp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png" width="1456" height="1260" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1260,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:314254,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.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_!niTp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png 424w, https://substackcdn.com/image/fetch/$s_!niTp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png 848w, https://substackcdn.com/image/fetch/$s_!niTp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png 1272w, https://substackcdn.com/image/fetch/$s_!niTp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F851fcdc5-4257-4bdf-a1a0-09ac06099054_1572x1360.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>From the external agent team</span></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!L0jR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!L0jR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png 424w, https://substackcdn.com/image/fetch/$s_!L0jR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png 848w, https://substackcdn.com/image/fetch/$s_!L0jR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png 1272w, https://substackcdn.com/image/fetch/$s_!L0jR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!L0jR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png" width="1456" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:201930,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.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_!L0jR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png 424w, https://substackcdn.com/image/fetch/$s_!L0jR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png 848w, https://substackcdn.com/image/fetch/$s_!L0jR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png 1272w, https://substackcdn.com/image/fetch/$s_!L0jR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86f8c9e8-3550-46e3-ae8f-09d5ff02abbc_1574x830.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>Setup steps on the Databricks side</span></h3><ol><li><p><span>Get redirect URLs and IPs from the external agent team.</span></p></li><li><p><span>Create the OAuth app: Account Console &gt; Settings &gt; App Connections &gt; Add connection.</span></p></li></ol><ul><li><p><span>Name: descriptive (e.g., foundry-mcp-client)</span></p></li><li><p><span>Redirect URLs: from agent team</span></p></li><li><p><span>Client type: as specified by agent team</span></p></li><li><p><span>Scopes: combine as needed</span></p></li></ul><ol><li><p><span>Allowlist IPs in the workspace IP access list (if applicable).</span></p></li><li><p><span>Grant UC permissions: ensure users or SPs have access to the vector search index, Genie space, UC functions, or tables.</span></p></li><li><p><span>Share credentials: send the agent team the client ID, endpoints, and MCP server URLs.</span></p></li><li><p><span>Configure AI Gateway: set up rate limits, enable usage tracking.</span></p></li></ol><p><span>Automation-friendly OAuth app creation via CLI:</span></p><p><code>databricks account custom-app-integration create --json &#8216;{</code></p><p><code>  &#8220;name&#8221;: &#8220;foundry-mcp-client&#8221;,</code></p><p><code>  &#8220;redirect_urls&#8221;: [&#8221;https://&lt;redirect-from-agent-team&gt;&#8221;],</code></p><p><code>  &#8220;confidential&#8221;: true,</code></p><p><code>  &#8220;scopes&#8221;: [&#8221;vector-search&#8221;, &#8220;genie&#8221;, &#8220;unity-catalog&#8221;, &#8220;offline_access&#8221;]</code></p><p><code>}&#8217;</code></p><h2><span>The Decision Tree at a Glance</span></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rY7L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rY7L!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png 424w, https://substackcdn.com/image/fetch/$s_!rY7L!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png 848w, https://substackcdn.com/image/fetch/$s_!rY7L!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png 1272w, https://substackcdn.com/image/fetch/$s_!rY7L!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!rY7L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png" width="1456" height="1083" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1083,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:314101,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.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_!rY7L!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png 424w, https://substackcdn.com/image/fetch/$s_!rY7L!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png 848w, https://substackcdn.com/image/fetch/$s_!rY7L!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png 1272w, https://substackcdn.com/image/fetch/$s_!rY7L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32bb2cee-ef17-4aef-8bd2-482677d858ec_1860x1384.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><span>Common Scenarios (Quick Paths)</span></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o3Et!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o3Et!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png 424w, https://substackcdn.com/image/fetch/$s_!o3Et!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png 848w, https://substackcdn.com/image/fetch/$s_!o3Et!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png 1272w, https://substackcdn.com/image/fetch/$s_!o3Et!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!o3Et!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png" width="1456" height="759" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:759,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:187347,&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://www.databricksters.com/i/203388920?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.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_!o3Et!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png 424w, https://substackcdn.com/image/fetch/$s_!o3Et!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png 848w, https://substackcdn.com/image/fetch/$s_!o3Et!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png 1272w, https://substackcdn.com/image/fetch/$s_!o3Et!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f5a06f-e2bb-45c0-97f1-09c6834c709b_1576x822.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2><span>Closing Thoughts</span></h2><p><span>Working through these six questions before writing any code keeps you from over-engineering a Supervisor Agent when a single MCP URL would have done the job, or from committing to OBO before confirming that your agent framework supports authorization code flow.</span></p><p><span>If you take only one thing from this post: answer the cross-tenant question early. Tenant topology determines whether you need a federation policy, and federation policies require account admin access on the Databricks side. Discovering that requirement after you have built half the integration is painful.</span></p><p><span>Done well, none of this complexity ever reaches the user. They open Teams, ask &#8220;what was our on-time delivery rate in the Northeast last week,&#8221; and get back numbers that respect their permissions, their region, their role. They never see the OAuth flow, the supervisor routing, or the federation policy. They just get the answer where they were already working. That is the whole point.</span></p><h2><span>References</span></h2><ul><li><p><a href="https://docs.databricks.com/generative-ai/mcp/managed-mcp/"><span>Use Databricks managed MCP servers</span></a></p></li><li><p><a href="https://docs.databricks.com/generative-ai/mcp/connect-external-services/"><span>Connect non-Databricks clients to Databricks MCP servers</span></a></p></li><li><p><a href="https://docs.databricks.com/generative-ai/agent-framework/agent-authentication-model-serving/"><span>Authentication for AI agents (Model Serving)</span></a></p></li><li><p><a href="https://docs.databricks.com/dev-tools/auth/oauth-federation/"><span>OAuth token federation</span></a></p></li><li><p><a href="https://docs.databricks.com/dev-tools/auth/oauth-federation-policy/"><span>Configure a federation policy</span></a></p></li><li><p><a href="https://docs.databricks.com/generative-ai/agent-bricks/multi-agent-supervisor/"><span>Supervisor Agent (multi-agent system)</span></a></p></li><li><p><a href="https://docs.databricks.com/generative-ai/mcp/custom-mcp/"><span>Host custom MCP servers using Databricks Apps</span></a></p></li><li><p><a href="https://docs.databricks.com/generative-ai/agent-framework/multi-agent-apps/"><span>Build a multi-agent system on Databricks Apps</span></a></p></li><li><p><a href="https://docs.databricks.com/ai-gateway/"><span>Unity AI Gateway</span></a></p></li><li><p><a href="https://docs.databricks.com/generative-ai/agent-framework/unstructured-retrieval-tools/"><span>Connect agents to unstructured data (Vector Search)</span></a></p></li></ul><h2><span>Glossary</span></h2><p><strong><span>Term</span></strong></p><p><strong><span>Definition</span></strong></p><p><strong><span>MCP</span></strong></p><p><span>Model Context Protocol, an open standard for connecting AI agents to tools and data sources</span></p><p><strong><span>OBO</span></strong></p><p><span>On-behalf-of authentication: the agent acts with the end user&#8217;s identity and permissions</span></p><p><strong><span>Token Federation</span></strong></p><p><span>Exchanging an external IdP JWT for a Databricks OAuth token without storing Databricks secrets</span></p><p><strong><span>AI Gateway</span></strong></p><p><span>Databricks&#8217; central governance layer for LLM endpoints, MCP servers, and coding agents</span></p><p><strong><span>Supervisor Agent</span></strong></p><p><span>A multi-agent orchestrator that routes queries to specialized sub-agents behind a single endpoint</span></p>]]></content:encoded></item><item><title><![CDATA[Reclaim Spend from Idle Databricks Apps]]></title><description><![CDATA[A daily governance workflow combining System Tables and OpenTelemetry]]></description><link>https://www.databricksters.com/p/reclaim-spend-from-idle-databricks</link><guid isPermaLink="false">https://www.databricksters.com/p/reclaim-spend-from-idle-databricks</guid><dc:creator><![CDATA[Sonakshi Pandey]]></dc:creator><pubDate>Tue, 09 Jun 2026 15:49:24 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/2018d97b-ca3d-4c4d-9127-7832e01143ad_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>TL;DR:  A workflow that finds and stops idle apps</strong></h2><p style="text-align: justify;">Databricks Apps adoption can outpace the visibility into what&#8217;s running, who owns each app, and what it&#8217;s costing. A workspace can quickly accumulate hundreds of apps across business teams - some delivering ongoing value, many running 24/7 long after anyone last touched them, with the compute bill climbing alongside.</p><p style="text-align: justify;"> Effective app management requires two complementary signals:</p><ul><li><p style="text-align: justify;"><strong>System tables</strong> : for inventory, ownership, lifecycle events, and cost attribution</p></li><li><p style="text-align: justify;"><strong>OpenTelemetry export to Unity Catalog</strong> : for request-level usage and idle detection [At the time this blog was published, App telemetry is in  <a href="https://docs.databricks.com/aws/en/release-notes/release-types">Public Preview</a>.]</p></li></ul><p style="text-align: justify;">Used together, these answer the full set of questions a platform team needs: what do we have, who owns it, what does it cost, and is anyone using it? This post walks through both layers and shows how to combine them into a daily workflow that includes automated idle-app shutdown.</p><h2><strong>Pillar 1: Governance and Cost Attribution with System Tables</strong></h2><p style="text-align: justify;"><a href="https://docs.databricks.com/aws/en/admin/system-tables/">System tables</a> provide the foundation for Apps governance. Four tables, combined, answer most operational questions:</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_5j!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b_5j!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png 424w, https://substackcdn.com/image/fetch/$s_!b_5j!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png 848w, https://substackcdn.com/image/fetch/$s_!b_5j!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png 1272w, https://substackcdn.com/image/fetch/$s_!b_5j!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!b_5j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png" width="1404" height="426" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:426,&quot;width&quot;:1404,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:66360,&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://www.databricksters.com/i/200801782?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.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_5j!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png 424w, https://substackcdn.com/image/fetch/$s_!b_5j!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png 848w, https://substackcdn.com/image/fetch/$s_!b_5j!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.png 1272w, https://substackcdn.com/image/fetch/$s_!b_5j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e1853d7-2646-4b3e-82d6-1ab1da03649d_1404x426.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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><strong>Questions this layer answers</strong></h3><p style="text-align: justify;">System tables provide the core signals needed to understand the operational footprint of Databricks Apps across a workspace:</p><ul><li><p><strong>Ownership :</strong> Creation events help identify the original app owner, while ACL change events help surface who currently has management access.</p></li><li><p><strong>Cost :</strong> Usage data from system.billing.usage, combined with pricing data from system.billing.list_prices, makes it possible to estimate app cost over time.</p></li><li><p><strong>Orphaned Apps: </strong> Apps with no apparent current manager can be flagged for review as potentially orphaned.</p></li><li><p><strong>Deletion history :</strong> Delete events provide a historical view of app removals, including timing and actor.</p></li><li><p><strong>Recreate cycles:</strong> Creation and deletion history can reveal create &#8594; delete &#8594; recreate patterns that may indicate experimentation, churn, or unstable ownership.</p></li></ul><p style="text-align: justify;">System tables answer <em>what do we have and what does it cost.</em> The next dimension - <em>is anyone actually using it</em> - requires a usage signal.</p><h2><strong>Pillar 2: Usage Signals with OpenTelemetry</strong></h2><p style="text-align: justify;"><a href="https://docs.databricks.com/aws/en/dev-tools/databricks-apps/observability">Databricks Apps support native OpenTelemetry</a> export to Unity Catalog. Three telemetry tables can be configured for an app, but they differ in what is required to populate them:</p><ul><li><p style="text-align: justify;">otel_logs - request logs and platform-emitted auth events. <strong>Populated automatically once OTel export is configured and the app is redeployed.</strong> No code changes required.</p></li><li><p style="text-align: justify;">otel_metrics - request counts, latencies, and other application-level metrics. <strong>Only populated after custom instrumentation is added to the app code</strong> (e.g. using the OpenTelemetry SDK in the application).</p></li><li><p style="text-align: justify;">otel_spans - distributed traces. <strong>Only populated after custom instrumentation is added to the app code.</strong></p></li></ul><p style="text-align: justify;">For governance use cases - and idle detection in particular - otel_logs is the primary signal, and the one that works out of the box. Each request lands in otel_logs with an app.name attribute and timestamp, providing per-request activity data that is independent of compute state.</p><p style="text-align: justify;">If teams add custom instrumentation later, otel_metrics and otel_spans open up deeper analysis (latency SLOs, error budgets, user journey traces), but they are not required for the workflow described in this post.</p><h3><strong>Enabling OTel export on an app</strong></h3><p style="text-align: justify;">Enablement is a single asynchronous update call per app, using the <strong>:createUpdate</strong> endpoint with an explicit <strong>update_mask</strong>.</p><p style="text-align: justify;">&#8252;&#65039;Do not use the direct <strong>PATCH /api/2.0/apps/{name}</strong> endpoint for this purpose because it performs a full replacement of the app object, which will silently wipe resources, env_vars, and permission attachments when those fields aren&#8217;t included in the request body. :createUpdate with <strong>update_mask: &#8220;telemetry_export_destinations&#8221;</strong> ensures only that field is modified.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;e02f76c5-30da-4009-b878-8a5b6f60ddfb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">
def patch_telemetry(app_name):
   """Enables OpenTelemetry export to Unity Catalog for the specified Databricks App."""
   payload = {
       "app": {
           "telemetry_export_destinations": [
               {"unity_catalog": TELEMETRY_TABLES}
           ]
       },
       "update_mask": "telemetry_export_destinations"
   }
   return requests.post(
       f"{workspace_url}/api/2.0/apps/{app_name}:createUpdate",
       headers=headers,
       json=payload,
   )</code></pre></div><p>Looping this over <code>GET /api/2.0/apps</code> lets you enable telemetry across the full app fleet. After each app is redeployed, otel_logs begins populating automatically once the app receives traffic, while <strong>otel_metrics </strong>and<strong> otel_spans</strong> remain empty until the application code is instrumented.</p><h3><strong>What OTel adds beyond billing data</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7KpS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7KpS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png 424w, https://substackcdn.com/image/fetch/$s_!7KpS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png 848w, https://substackcdn.com/image/fetch/$s_!7KpS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png 1272w, https://substackcdn.com/image/fetch/$s_!7KpS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!7KpS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png" width="1406" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9276db53-0b61-40f1-9110-1451c6349564_1406x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1406,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:92720,&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://www.databricksters.com/i/200801782?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.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_!7KpS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png 424w, https://substackcdn.com/image/fetch/$s_!7KpS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png 848w, https://substackcdn.com/image/fetch/$s_!7KpS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png 1272w, https://substackcdn.com/image/fetch/$s_!7KpS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9276db53-0b61-40f1-9110-1451c6349564_1406x640.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>Both layers are needed for confident operational decisions. An app may be running and accruing DBUs while no user has touched it for weeks.</p><h3>&#128680; Caveats and Operational Considerations with OTel</h3><ul><li><p style="text-align: justify;"><strong>Cold-start gap after OTel enablement. </strong>The workflow trusts otel_logs to mean &#8216;this app had no requests in the activity window.&#8217; If OTel export was just enabled, no telemetry has had time to accumulate - every app looks idle, and a first-day run will flag every app for shutdown. Don&#8217;t act on the workflow&#8217;s recommendations until telemetry has been collecting for at least as long as your idle-detection window.</p></li><li><p style="text-align: justify;"><strong>PII in telemetry. OTel auto-instrumentation captures request URLs</strong>, query parameters, headers, and request bodies. If apps pass user data through URLs, it will land in <strong>otel_logs</strong>. Apply row-level security on telemetry tables from day one.</p></li><li><p style="text-align: justify;"><strong>Staggered enablement and redeployment.</strong> Patching telemetry config and redeploying causes a user-visible app restart. For workspaces with hundreds of apps, schedule the enablement in off-hours batches and stage tier-1 apps separately to minimize disruption.</p></li><li><p style="text-align: justify;"><strong>Explicit protection for critical apps.</strong> Maintain a <strong>PROTECTED_APPS</strong> list in the shutdown job. Incident-response tools, on-call dashboards, and tier-1 business apps should be hard-coded as exempt regardless of activity level.</p></li><li><p><strong>Telemetry table retention.otel_logs</strong> grows quickly at scale. Set retention windows and <strong>VACUUM</strong> schedules on day one, partition by date, and monitor storage cost. Otherwise, telemetry storage will offset the savings from idle-app shutdown.</p></li></ul><h2><strong>Governance using System Tables and OTel</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QyEE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QyEE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png 424w, https://substackcdn.com/image/fetch/$s_!QyEE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png 848w, https://substackcdn.com/image/fetch/$s_!QyEE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png 1272w, https://substackcdn.com/image/fetch/$s_!QyEE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!QyEE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!QyEE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png 424w, https://substackcdn.com/image/fetch/$s_!QyEE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png 848w, https://substackcdn.com/image/fetch/$s_!QyEE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png 1272w, https://substackcdn.com/image/fetch/$s_!QyEE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82b6fe97-81aa-424e-b8cf-37ed7a3bdb0b_2048x1117.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 style="text-align: justify;"><strong>With both System tables and OTel metrics in place, identifying and stopping idle apps becomes a 5-step workflow.</strong></p><h3>Idle Detection and Shutdown in 5 simple steps</h3><h4>Step 1: Find apps with recent activity</h4><p style="text-align: justify;">Query the OpenTelemetry logs for any app that has had user requests or auth events in the activity window. A 7-day window is a safe production default; tighten only after observing baseline traffic patterns.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;d5f29edf-77ed-4350-8b25-bb03ee6b7929&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">WITH activity AS (
SELECT DISTINCT
  COALESCE(
    service_name,
    variant_get(resource.attributes, '$["app.name"]', 'STRING')
  ) AS app_name
FROM my_catalog.my_schema.otel_logs
WHERE `time` &gt;= current_timestamp() - INTERVAL 7 DAYS
)
SELECT app_name FROM activity WHERE app_name IS NOT NULL</code></pre></div><h4>Step 2: Compute idle candidates</h4><p>Combine the activity set with the live app inventory (<code>GET /api/2.0/apps</code>) and apply three safety filters: only <code>ACTIVE</code> apps are candidates, anything in <code>PROTECTED_APPS</code> is exempt regardless of activity, and any app younger than <code>MIN_AGE_DAYS</code> is skipped so newly-deployed apps have room to ramp.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;c6b43d93-30b3-409e-a4fd-453a639f57ad&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from datetime import datetime, timezone
from dateutil.parser import parse as parse_ts

PROTECTED_APPS = {
    "oncall-dashboard",
    "incident-router",
    "exec-monitoring",
}
MIN_AGE_DAYS = 7   # grace period for newly-deployed apps

active = {
    row["app_name"]
    for row in spark.sql("&lt;Step 1 SQL&gt;").collect()
}

candidates = []
for app in list_all_apps():
    state = (app.get("compute_status") or {}).get("state")
    age_days = (datetime.now(tz=timezone.utc) - parse_ts(app["create_time"])).days
    if (
        state == "ACTIVE"
        and app["name"] not in active
        and app["name"] not in PROTECTED_APPS
        and age_days &gt;= MIN_AGE_DAYS
    ):
        candidates.append(app["name"])
</code></pre></div><h4>Step 3: Resolve owners and notify</h4><p style="text-align: justify;">This step combines both pillars: query system.access.audit (Pillar 1) to derive a per-app ownership table, then resolve owners for the candidate set generated from OpenTelemetry logs (Pillar 2), and send a 7-day warning to each candidate&#8217;s owner. The notification mechanism is intentionally a placeholder - Slack, email, or a Databricks Apps inbox notification all work.  The audit trail produced in Step 4 is what matters.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;b871d7cc-7adf-467a-a0fd-4b54d2812a13&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Build the per-app ownership table from System table system.access.audit.
# Run this once during setup; refresh weekly via a separate job.

spark.sql("""
  CREATE OR REPLACE TABLE apps_ownership AS
  WITH created_latest AS (
    SELECT
      get_json_object(request_params['app'], '$.name') AS app_name,
      workspace_id,
      user_identity.email AS creator_email,
      ROW_NUMBER() OVER (
        PARTITION BY get_json_object(request_params['app'], '$.name'),
                     workspace_id
        ORDER BY event_time DESC
      ) AS rn
    FROM system.access.audit
    WHERE service_name = 'apps'
      AND action_name = 'createApp'
      AND event_date &gt;= current_date() - INTERVAL 365 DAYS
  )
  SELECT app_name, workspace_id, creator_email FROM created_latest WHERE rn = 1
""")

# Daily run: scope the ownership lookup to today's candidate set
spark.createDataFrame(
    [(name,) for name in candidates],
    ["app_name"],
).createOrReplaceTempView("idle_app_candidates")

owners = (
    spark.sql("SELECT app_name, creator_email FROM apps_ownership WHERE app_name IN (SELECT app_name FROM idle_app_candidates) AND workspace_id = '&lt;your workspace_id&gt;'")
    .toPandas()
    .set_index("app_name")["creator_email"]
    .to_dict()
)

for name in candidates:
    owner = owners.get(name, ADMIN_FALLBACK_EMAIL)
    notify(  # replace with your Slack / email / paging integration
        to=owner,
        subject=f"App '{name}' scheduled for shutdown in 7 days",
        body=(
            "No activity detected in the last 7 days. "
            "Touch the app or add it to PROTECTED_APPS to keep it running."
        ),
    )</code></pre></div><h4>Step 4: Reconcile the journal and enforce the warning period</h4><p style="text-align: justify;">This step does two things to the journal:</p><ul><li><p style="text-align: justify;">First, it sweeps stale rows - any journal entry whose app showed activity in the current window is deleted, since the warning&#8217;s premise no longer holds. This includes rows from previously-stopped apps whose owners have restarted them, ensuring the reactivation is acknowledged and a fresh warning cycle begins if the app later goes idle again.</p></li><li><p style="text-align: justify;">Second, it MERGEs today&#8217;s idle candidates into the journal. The actual stop call in Step 5 only fires for apps that have been on the journal for at least WARNING_DAYS and are still idle on the next run, so this guarantees no app is stopped without a full warning period elapsing, and an app that bounces between active and idle starts each new idle cycle with a fresh <code>warning_sent_at</code>.</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;53a1ec59-fe27-45cd-a03f-238faab2d98a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Sweep stale journal entries first.
spark.createDataFrame(
    [(n,) for n in active],
    ["app_name"],
).createOrReplaceTempView("active_apps_today")

spark.sql("""
  DELETE FROM shutdown_journal
  WHERE app_name IN (SELECT app_name FROM active_apps_today)
""")

# Then MERGE today's warnings (idle_app_candidates was registered in Step 3).
spark.sql("""
  MERGE INTO shutdown_journal target
  USING (
    SELECT app_name, current_timestamp() AS warning_sent_at
    FROM idle_app_candidates
  ) source
  ON target.app_name = source.app_name
  WHEN NOT MATCHED THEN INSERT (app_name, warning_sent_at, stopped_at, status)
    VALUES (source.app_name, source.warning_sent_at, NULL, NULL)
""")</code></pre></div><h4>Step 5: Stop expired candidates with an audit record</h4><p style="text-align: justify;">For each app that&#8217;s been in the journal at least <code>WARNING_DAYS</code> and is still idle and still not protected, call the stop endpoint and write the outcome back to the journal. </p><p style="text-align: justify;">&#8252;&#65039; <strong>Stop, don&#8217;t delete</strong> - stopping is reversible, deletion is not.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;899c0648-8a0d-4247-9310-19bd9eb079cb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">expired = spark.sql("""
  SELECT app_name
  FROM shutdown_journal
  WHERE warning_sent_at &lt;= current_timestamp() - INTERVAL 7 DAYS
    AND stopped_at IS NULL
""").collect()

for row in expired:
    name = row["app_name"]
    if name in active or name in PROTECTED_APPS:
        continue  # last-second safety check
    r = requests.post(
        f"{workspace_url}/api/2.0/apps/{name}/stop",
        headers=headers,
        timeout=60,
    )
    spark.sql(
        "UPDATE shutdown_journal "
        "SET stopped_at = current_timestamp(), status = :status "
        "WHERE app_name = :name",
        args={"status": str(r.status_code), "name": name},
    )
</code></pre></div><h4>Orchestration </h4><p style="text-align: justify;">Wrap the five steps in a single <a href="https://docs.databricks.com/aws/en/jobs/configure-job#create-a-new-job">Databricks Job</a>:</p><ul><li><p style="text-align: justify;"><strong>Cadence:</strong> daily, off-hours (e.g., 02:00 UTC) so restart impact lands outside business hours</p></li><li><p style="text-align: justify;"><strong>Identity:</strong> a dedicated service principal (apps-governance-sp) with the minimum permissions:</p><ul><li><p style="text-align: justify;"><code>SELECT</code> on otel_logs and apps_ownership</p></li><li><p style="text-align: justify;"><code>SELECT / INSERT / UPDATE /DELETE</code> on shutdown_journal</p></li><li><p style="text-align: justify;"><code>CAN_MANAGE</code> on the apps you intend to govern (required to call /stop)</p></li></ul></li><li><p style="text-align: justify;"><strong>Parameters:</strong> <code>DRY_RUN</code> (default true for the first two weeks of rollout), <code>MIN_AGE_DAYS</code> (default 7), WARNING_DAYS (default 7)</p></li><li><p style="text-align: justify;"><strong>Alerts:</strong> route job failures to the platform-team channel. A silent failure of this job means orphaned compute keeps running - the cost of missed runs is real money.</p></li><li><p style="text-align: justify;">Materialization: keep <strong>apps_ownership</strong> as a Delta table (not a view), refreshed weekly by a separate job. Daily shutdown runs read from the pre-materialized object in milliseconds instead of re-scanning audit logs every time. Apps younger than <code>MIN_AGE_DAYS</code> can&#8217;t be candidates anyway, so weekly refresh is sufficient.</p></li></ul><h3>Apps monitoring dashboard</h3><p>The daily shutdown job is one side of the governance loop, an <strong>automated action</strong>. The other side is <strong>decision support</strong>: <a href="https://docs.databricks.com/aws/en/dashboards">Databricks dashboard</a> that surfaces the same signals to platform reviewers, app owners, and executives.</p><p> Built on the same two pillars, an account-wide dashboard answers the questions the platform team gets asked every week:</p><ul><li><p><strong>Inventory KPIs:</strong> total apps, active vs stopped vs error states, OTel enablement coverage</p></li><li><p><strong>Cost trend:</strong> weekly DBU consumption per app, with hover-to-USD via system.billing.list_prices</p></li><li><p><strong>Usage signals:</strong> apps used in the last 7 days, apps unused in the last 7 days (the shutdown candidate set), broken down by workspace</p></li><li><p><strong>Lineage/ownership:</strong> owner email, last access, last admin action - all derived from system.access.audit</p></li></ul><p>Admins see a single account-wide view instead of N per-workspace views. The visual interface turns raw signal into context that lets people decide <em>which</em> apps to keep, retire, or invest in - without manually correlating audit logs, OTel data, and billing tables.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mR4G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mR4G!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png 424w, https://substackcdn.com/image/fetch/$s_!mR4G!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png 848w, https://substackcdn.com/image/fetch/$s_!mR4G!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png 1272w, https://substackcdn.com/image/fetch/$s_!mR4G!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!mR4G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png" width="1456" height="1172" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1172,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!mR4G!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png 424w, https://substackcdn.com/image/fetch/$s_!mR4G!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png 848w, https://substackcdn.com/image/fetch/$s_!mR4G!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png 1272w, https://substackcdn.com/image/fetch/$s_!mR4G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcee42077-5be7-453e-929a-c4bbadbaf3c1_2048x1649.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><h2><strong>Summary</strong></h2><p style="text-align: justify;">A complete governance posture for Databricks Apps combines three building blocks:</p><ol><li><p style="text-align: justify;"><strong>System tables</strong> for inventory, ownership, cost, lifecycle, and compliance reporting.</p></li><li><p style="text-align: justify;"><strong>OpenTelemetry `otel_logs` export</strong> for request-level usage and idle detection - available out of the box once OTel is configured. otel_metrics and otel_spans add deeper application-level observability when teams add custom instrumentation.</p></li><li><p style="text-align: justify;"><strong>Automated controls</strong> - a daily job that identifies idle apps, notifies owners, and stops stale apps.</p></li></ol><p>System tables answer <em>what do we have,</em> and <em>what does it cost. </em>OpenTelemetry tells you whether<em> anyone is using it.</em> <strong>Together, the two enable platform teams to support broad Apps adoption while keeping costs predictable and ownership accountable.</strong></p><h2><strong>FAQ</strong></h2><p><strong>1. Why aren&#8217;t system tables alone enough to find idle apps?</strong></p><p>System tables show inventory, ownership, lifecycle events, and billing, but they do not reliably show whether users are actively interacting with an app. For idle detection, you need request-level activity from telemetry.</p><p><strong>2. Which OpenTelemetry table is most important for this workflow?</strong></p><p><code>otel_logs</code> is the key table because it captures request and auth activity without requiring application code changes, once telemetry export is configured and the app is redeployed.</p><p><strong>3. Why should the workflow stop apps instead of deleting them?</strong></p><p>Stopping is reversible and preserves the app for later use. Deletion is permanent and introduces unnecessary operational risk for an automated governance process.</p><p><strong>4. What is the biggest rollout risk when enabling telemetry?</strong></p><p>If you run idle detection immediately after enabling telemetry, every app can appear idle because no activity history exists yet. Wait at least as long as your idle window before enforcing shutdown actions.</p><p><strong>5. How should critical apps be handled?</strong></p><p>Maintain an explicit protected-apps allowlist so business-critical or operational apps are never stopped automatically, regardless of recent activity.</p><p><strong>6. How often should the idle-app workflow run?</strong></p><p>A daily off-hours run is a practical default. It gives enough frequency to control spend without creating unnecessary operational churn.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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[Three decisions that make or break your production agent]]></title><description><![CDATA[A practitioner's framework for the three forks in the road that actually matter &#8212; tackle them in whatever order makes sense for your team]]></description><link>https://www.databricksters.com/p/three-decisions-that-make-or-break</link><guid isPermaLink="false">https://www.databricksters.com/p/three-decisions-that-make-or-break</guid><dc:creator><![CDATA[Austin]]></dc:creator><pubDate>Thu, 28 May 2026 15:02:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f0ff43c1-d35b-4c84-9a9b-6995c1bf4890_1376x768.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1><strong>TL;DR</strong></h1><ul><li><p>Most production agent failures come from three architectural decisions: <strong>data serving path</strong>, <strong>data access abstraction</strong>, and <strong>observability</strong>.</p></li><li><p>Start with the <strong>simplest serving path that meets latency needs</strong>: use SQL Warehouse first, optimize table layout and queries, then add session caching or OLTP only if measurements justify it.</p></li><li><p>Put all data access behind <strong>one execution interface</strong> like <code>execute_sql(...)</code> so you can centralize routing, caching, retries, parameterization, and safety controls.</p></li><li><p>Treat observability as a launch requirement: <strong>trace tool calls, store traces in queryable tables, and add a synthetic heartbeat</strong> to measure true end-to-end freshness and latency.</p></li><li><p>The winning pattern is not &#8220;most advanced architecture first&#8221;; it is <strong>measure, optimize the hot path, and preserve the ability to swap components without rewriting the agent</strong>.</p><p></p></li></ul><h2>The Framework</h2><p>You can stand up an agent in an afternoon. Standing one that holds latency SLAs under real concurrency is a different exercise &#8212; one most teams underestimate.</p><p>The good news: most production agents need to talk to data and they will eventually run into the same three forks in the road. Pick well on each, and most of the other problems become tractable. Pick poorly, and you&#8217;ll be rebuilding under pressure.</p><p>This is a practitioner&#8217;s framework for the three decisions. They&#8217;re independent &#8212; you can tackle them in any order, and most teams end up working on all three in parallel. What matters is that you&#8217;ve made a deliberate call on each one.</p><h2><strong>Decision 1: How will the agent fetch data?</strong></h2><p>The instinct here is to jump to the most complex serving pattern before tuning the simplest one. Resist it.</p><p>You have three realistic options:</p><ul><li><p><strong>Option A &#8212; Direct SQL Warehouse Serverless.</strong> Your tool calls go straight to a SQL warehouse. No new infrastructure, no reverse ETL. Worth knowing: Serverless  keeps a remote result cache that survives warehouse shutdowns, so repeat queries stay fast even after the warehouse scales to zero. The fastest thing to ship.</p></li><li><p><strong>Option B &#8212; In-session caching.</strong> If your agent makes repeated queries against the same dataset within a single session, fetch it once and cache it in the session so subsequent calls don&#8217;t re-hit the warehouse. The implementation can be as simple as a dict or a DataFrame in the agent process, or an embedded SQL engine like DuckDB if you need to issue varied SQL against the cached data. The decision is about <em>recognizing intra-session repetition</em>, not about picking a cache library.</p></li><li><p><strong>Option C &#8212; Lakebase (OLTP).</strong> You reverse-ETL the tables the agent reads into Databricks Lakebase, a managed Postgres, and the agent queries Postgres instead of Delta. Sub-50ms point lookups. Highest performance ceiling, highest operational cost. Lakebase also doubles as a place to store online features for sub-second feature lookups and can persist agent session state &#8212; if you&#8217;re already running it for those reasons, the marginal cost of using it as your serving layer drops.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wtgf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wtgf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png 424w, https://substackcdn.com/image/fetch/$s_!wtgf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png 848w, https://substackcdn.com/image/fetch/$s_!wtgf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png 1272w, https://substackcdn.com/image/fetch/$s_!wtgf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!wtgf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png" width="1456" height="852" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:852,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!wtgf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png 424w, https://substackcdn.com/image/fetch/$s_!wtgf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png 848w, https://substackcdn.com/image/fetch/$s_!wtgf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png 1272w, https://substackcdn.com/image/fetch/$s_!wtgf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a31680-b4eb-426a-950a-ae33d1eeeeea_1462x856.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><strong>The mistake I see most:</strong> teams pattern-match on &#8220;we need low latency&#8221; and jump to Option C before they&#8217;ve measured Option A. They end up running a reverse-ETL pipeline they didn&#8217;t need.</p><p>Here&#8217;s the order that actually works:</p><ol><li><p><strong>Start with Option A.</strong> Get the agent end-to-end on SQL Warehouse/ DBSQL  Serverless. Size the warehouse sensibly &#8212; Medium for dev, Large for prod is a reasonable starting point &#8212; X-Large gets expensive fast and is rarely the right place to begin. Start with min 1, max 3&#8211;4 clusters, then watch the query history; if queries are getting queued, add a cluster. Keep the warehouse warm if you can&#8217;t tolerate cold-start tail latency on the first query after a scale-down.</p></li><li><p><strong>Tune the simple path before adding a complex one.</strong> Did you actually optimize the layout of the table the agent reads? Are your queries hitting the right clustering keys? Are you using native parameterized queries, not inline params? In our experience, half of the &#8220;we need OLTP&#8221; cases evaporate once the table layout is right.</p></li><li><p><strong>Only then evaluate B or C.</strong> The decision turns on access pattern, not vibes. If a single session issues several tool calls that hit the same underlying data, Option B&#8217;s in-session caching pays for itself &#8212; the trigger is repetition within a session, not session length. If your agent makes single point lookups with hard sub-second SLAs across thousands of concurrent sessions, Option C is the answer.</p></li></ol><p>A note on what actually matters for performance: it&#8217;s the engine serving the agent and the layout of the specific table that engine reads &#8212; nothing else. Medallion labels are organizational naming, not architecture. The agent doesn&#8217;t care what tier a table sits in; it cares whether the table it queries is laid out for fast reads. If your agent reads from a Delta table via SQL Warehouse/DBSQL, that t<a href="https://www.canadiandataguy.com/p/optimizing-delta-lake-tables-liquid">able&#8217;s clustering and file layout determine your latency.</a> If your agent reads from Lakebase, the Delta layout upstream is mostly irrelevant; what matters is the Postgres indexing and the sync freshness. Either way: optimize whatever the serving engine actually reads at query time, and let Predictive Optimization handle upstream tables that aren&#8217;t in the agent&#8217;s hot path. Avoid materialized views with non-deterministic refresh behavior anywhere in that hot path.</p><p>For Delta tables specifically, use <strong>liquid clustering</strong> and put real thought into the clustering keys. Sortable types make great keys &#8212; integers, dates, and timestamps in particular, because they have clean min/max statistics that let Delta skip files efficiently. String keys can work, but they&#8217;re far more sensitive to encoding choices (lexicographic ordering, padding, casing) and tend to underperform. Pick keys that match the actual filter patterns the agent will hit &#8212; usually some tenant or entity ID plus a time dimension &#8212; and verify the agent&#8217;s queries are pruning files the way you expect by reading the query profile.</p><p><strong>Quick tip:</strong> If you&#8217;re merging into a table continuously &#8212; say every 5 seconds &#8212; schedule explicit liquid clustering at a regular cadence (every 30&#8211;60 minutes). Otherwise you&#8217;re relying on Predictive Optimization to catch up on its own schedule, and for hot, agent-facing tables that&#8217;s usually not fast enough.</p><p><strong>Force yourself to write down, before you build:</strong></p><ul><li><p>The p95 latency budget per agent tool call, end-to-end, not per-query.</p></li><li><p>Average and p90 queries per agent session. If it&#8217;s two, the cache barely helps. If it&#8217;s ten, it&#8217;s a no-brainer.</p></li><li><p>The size of the largest object the agent will scan. Some OLTP stores have hard per-instance ceilings; if you&#8217;re past them, the architecture has to bend.</p></li></ul><h2><strong>Decision 2: How will you expose data to the agent?</strong></h2><p>Some tool in your agent that needs data &#8212;they needs a way to talk to the data plane. The instinct is to give each tool its own SQL connector when needed, its own connection management, its own retry logic. Don&#8217;t.</p><p><strong>Use one execution interface.</strong> A single function &#8212; call it execute_sql(query, max_staleness:int = 60) &#8212; that every data-bearing tool calls. Routing, caching, parameterization, observability, retries: all internal to that one function. Tools pass SQL, get results back, and don&#8217;t know or care whether the query went to SQL Warehouse, an in-memory cache, or Lakebase.</p><p>Without abstraction each tool manages its own connector, retries, parameters. With execute_sql One place to route, optimize, and enforce safety. Without the abstraction, every tool reinvents connection management. With it, you change one function to swap engines, tune queries, or harden security.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yy77!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yy77!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png 424w, https://substackcdn.com/image/fetch/$s_!yy77!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png 848w, https://substackcdn.com/image/fetch/$s_!yy77!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png 1272w, https://substackcdn.com/image/fetch/$s_!yy77!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!yy77!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png" width="1456" height="785" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:785,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!yy77!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png 424w, https://substackcdn.com/image/fetch/$s_!yy77!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png 848w, https://substackcdn.com/image/fetch/$s_!yy77!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png 1272w, https://substackcdn.com/image/fetch/$s_!yy77!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac67b8f-2159-495f-9688-006f2a3877e4_1588x856.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>This is the single highest-leverage abstraction you can put in your agent. Three reasons:</p><ul><li><p><strong>One place to optimize.</strong> Tuning one query path improves every tool simultaneously. Tuning nine separate paths means you&#8217;ll tune three and forget the rest.</p></li><li><p><strong>One place to swap implementations.</strong> When you decide to add caching (Decision 1, Option B) or move to Lakebase (Option C), no tool code changes. You rewrite execute_sql and you&#8217;re done.</p></li><li><p><strong>One place to enforce safety.</strong> Parameterization, query limits, allowed-table lists &#8212; all enforced once.</p></li></ul><p>On safety specifically, two things to bake in from day one.</p><p><strong>Native parameterized queries, always.</strong> If you&#8217;re on databricks-sql-connector &gt;= 3.0, use paramstyle=&#8221;named&#8221;. Inline parameters bypass server-side caching, won&#8217;t survive a future connector release, and open up injection vectors. </p><p><strong>Keep sensitive identifiers out of the model&#8217;s parameter space.</strong> If your agent operates on a specific tenant, customer, or dataset, the model should not be the thing choosing which tenant to query. Use a factory/closure pattern: bind the tenant ID into the tool at construction time, so the model can only call the tool &#8212; it can&#8217;t substitute a different tenant via prompt injection. Save the parameterized version for arguments the model genuinely should control, like a column name to inspect.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;16c7b980-494c-4610-9b76-58796595ff26&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def make_detect_missing_data(tenant_id: str):
    @tool
    def detect_missing_data(column_name: str) -&gt; str:
        """Detect null values in a column."""
        return _run(tenant_id, column_name)  # tenant_id closed over
    return detect_missing_data</code></pre></div><p>Compare that to the naive pattern where tenant_id is a tool parameter the LLM fills in. The naive version is one cleverly-worded user message away from a tenant-isolation incident.</p><p><strong>Let the caller declare freshness tolerance.</strong> execute_sql should accept a max_staleness parameter &#8212; the maximum age of cached data the caller will tolerate for this query. If max_staleness is generous (say, 60 seconds), the query runner is free to serve from the in-session cache, the SQL Warehouse remote result cache, or a Lakebase replica with known lag. If it&#8217;s zero, the runner bypasses caches and goes to the source of truth. Th<strong>is pushes the consistency-vs-latency tradeoff to the call site, where the tool author actually knows whether eventual consistency is acceptable</strong>. Without it, you end up either over-caching (and serving stale answers in places that can&#8217;t tolerate them) or under-caching (and paying full warehouse latency for queries that didn&#8217;t need fresh data).</p><h2><strong>Decision 3: How will you know the agent is working?</strong></h2><p>If you can&#8217;t see what your agent is doing in production, you don&#8217;t have an agent &#8212; you have a black box that occasionally answers questions. M<strong>ost teams get tracing late, after the first incident. Don&#8217;t be that team.</strong></p><p><strong>Use MLflow tracing, and route it to Unity Catalog.</strong> MLflow&#8217;s tracing is OpenTelemetry-compatible. Link your experiment to a Unity Catalog trace location with </p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;a88208ae-20e8-4577-aded-81379008657b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">syntax trace_location=UnityCatalog(
catalog_name=catalog_name,
schema_name=schema_name,
table_prefix=table_prefix)</code></pre></div><p>and your traces land as Delta tables in UC that you can query with SQL. You&#8217;ll thank yourself the first time someone asks &#8220;show me every slow trace for tenant X in the last 24 hours.&#8221; Without UC-backed traces, that&#8217;s an afternoon of grepping logs. With them, it&#8217;s a SQL query.</p><p><strong>Instrument the right boundaries.</strong> At minimum: input validation, tool dispatch, each tool&#8217;s SQL execution, and the model call itself. If your agent framework supports autolog, lean on it &#8212; but verify it&#8217;s actually emitting the spans you expect. Auto-instrumentation has bugs and gets disabled by version mismatches. When autolog isn&#8217;t trustworthy in your stack, fall back to manual mlflow.start_span() for the boundaries that matter. Don&#8217;t trace everything; trace the things you&#8217;d want to filter on during an incident &#8212; tenant_id, query_pattern, tool_name.</p><p><strong>Add a synthetic heartbeat.</strong> Inject a known record at the source on a fixed cadence &#8212; every 30 or 60 seconds &#8212; and measure when it shows up at the agent. This is the one honest measure of end-to-end latency. System tables and per-stage metrics will tell you each component is healthy while the whole pipeline is silently 90 seconds behind. A heartbeat catches that immediately. It&#8217;s about a hundred lines of code; build it before you scale.</p><p><strong>Set a latency budget per stage and defend it.</strong> Source &#8594; ingestion &#8594; transformation &#8594; serving table &#8594; agent. Every stage gets a number. Every stage has an SLO. When something breaks, you know which stage to look at without guessing. The budget also forces an honest design conversation: if your end-to-end target is 30 seconds and CDC alone takes 12, you don&#8217;t have 18 seconds for everything else &#8212; you have to decide what to cut.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RWN2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RWN2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png 424w, https://substackcdn.com/image/fetch/$s_!RWN2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png 848w, https://substackcdn.com/image/fetch/$s_!RWN2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png 1272w, https://substackcdn.com/image/fetch/$s_!RWN2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!RWN2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png" width="1456" height="799" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.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;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!RWN2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png 424w, https://substackcdn.com/image/fetch/$s_!RWN2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png 848w, https://substackcdn.com/image/fetch/$s_!RWN2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png 1272w, https://substackcdn.com/image/fetch/$s_!RWN2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1169f96-4b28-41a0-82ea-b67e412ad3b8_1586x870.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>Three observability tactics in one picture: a per-stage budget you can defend, trace spans landing in queryable storage, and a heartbeat that measures end-to-end the same way a user would.</p><h2><strong>What this looks like in practice</strong></h2><p>Start simple. Ship Option A for serving. Wrap data access in execute_sql. Turn on MLflow tracing to Unity Catalog and add a heartbeat. Resist the urge to add caching, OLTP, or stateful streaming gymnastics until you&#8217;ve measured a real problem.</p><p>The teams that ship reliable agents they do it boringly. They benchmark before they scale. They write down their latency budget. They abstract their data access. They trace the things that matter.</p><p>The teams that ship unreliable agents skip ahead to the interesting parts and find out, in production, that the boring parts were the load-bearing parts.</p><p>You don&#8217;t need to pick the optimal architecture on day one. You need to pick one you can swap pieces of without rewriting everything. Get the three decisions above right and you will thank yourself later.</p><h2>FAQ</h2><h3><strong>1. What is the best default serving option for a new production agent?</strong></h3><p>Start with <strong>SQL Warehouse</strong>. It is the fastest path to production, requires the least new infrastructure, and is often sufficient once query patterns and table layout are optimized.</p><h3><strong>2. When should I add in-session caching?</strong></h3><p>Add it when a single agent session repeatedly queries the <strong>same underlying data</strong>. If sessions only issue one or two reads, caching usually adds complexity without much benefit.</p><h3><strong>3. When does an OLTP serving layer make sense?</strong></h3><p>Use it when you need <strong>very fast point lookups under high concurrency</strong> and have hard latency targets that the warehouse path cannot meet even after optimization.</p><h3><strong>4. Why should every tool call the same </strong><code>execute_sql(...)</code><strong> function?</strong></h3><p>A shared execution layer gives you <strong>one place</strong> to manage routing, retries, caching, parameterization, observability, and safety policies. It also makes future architecture changes much easier.</p><h3><strong>5. What is the biggest security mistake in agent data access?</strong></h3><p>Letting the model choose sensitive identifiers such as <strong>tenant or customer IDs</strong>. Those should be bound into the tool outside the model&#8217;s control to reduce isolation and prompt-injection risk.</p><h3><strong>6. What should I instrument first?</strong></h3><p>At minimum, trace <strong>input validation, tool dispatch, SQL execution, and model calls</strong>. Then add a synthetic heartbeat so you can measure actual end-to-end lag, not just component-level health.</p>]]></content:encoded></item><item><title><![CDATA[Multi-Genie Slack Bot: Production-Grade Conversational Analytics in Slack with Databricks Genie Spaces]]></title><description><![CDATA[Check out Databricks native Slack Integration before implementing a custom solution!]]></description><link>https://www.databricksters.com/p/multi-genie-slack-bot-production</link><guid isPermaLink="false">https://www.databricksters.com/p/multi-genie-slack-bot-production</guid><dc:creator><![CDATA[Ambarish]]></dc:creator><pubDate>Fri, 01 May 2026 15:02:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5ne9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<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_!5ne9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5ne9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!5ne9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!5ne9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!5ne9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5ne9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/05a5f66a-7283-47b9-b2b7-279a26945862_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;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!5ne9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!5ne9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!5ne9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!5ne9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05a5f66a-7283-47b9-b2b7-279a26945862_1536x1024.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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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><em>Check out Databricks native Slack Integration before implementing a custom solution! </em></p><p><em>https://docs.databricks.com/aws/en/genie-one/genie-slack </em> </p><p>Connect multiple Databricks Genie spaces to Slack with per-user authentication, intelligent routing, semantic caching, and audit logging &#8212; all deployed as a Databricks App. A production-grade Slack bot that brings Databricks Genie directly into Slack, allowing users to ask natural-language questions across multiple data domains without leaving their workspace.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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><h2><em>Special Thanks</em></h2><p>I extend my sincere thanks to <em><strong>Sunil Patil</strong></em> (<a href="https://www.linkedin.com/in/pppsunil/">https://www.linkedin.com/in/pppsunil/</a>) for his invaluable help during the brainstorming phase. His insights were instrumental in solving the complex challenge of managing multiple genies across multiple Slack channels. This project definitely levelled up thanks to his help!</p><h1>Features</h1><ul><li><p>Multi-Genie Support: Route questions to the right Genie space based on channel, thread, AI classification</p></li><li><p>Per-User Authentication: Use OAuth 2.0 User-to-Machine with PKCE so queries run under each user&#8217;s Unity Catalog permissions</p></li><li><p>Threaded Conversations: Maintain Genie context across Slack threads</p></li><li><p>Semantic Cache: Reuse answers for similar questions using Databricks Vector Search</p></li><li><p>Delta-Aware Cache Invalidation: Automatically invalidate cached answers when underlying Delta tables change</p></li><li><p>Audit Logging: Track user activity, cache hits, latency, errors, and Genie usage in Delta tables</p></li><li><p>Reliable Genie Calls: Handle rate limits with retry, exponential backoff, and adaptive polling</p></li><li><p>Native Deployment: Run the entire application as a Databricks App with no external infrastructure</p></li></ul><h1>Architecture</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6d3_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6d3_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png 424w, https://substackcdn.com/image/fetch/$s_!6d3_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png 848w, https://substackcdn.com/image/fetch/$s_!6d3_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png 1272w, https://substackcdn.com/image/fetch/$s_!6d3_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!6d3_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png" width="1456" height="800" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:800,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!6d3_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png 424w, https://substackcdn.com/image/fetch/$s_!6d3_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png 848w, https://substackcdn.com/image/fetch/$s_!6d3_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png 1272w, https://substackcdn.com/image/fetch/$s_!6d3_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4984365d-ea4d-48d0-aff7-1f56863a02b5_1692x930.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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 application runs as a Databricks App and uses two services:</p><ul><li><p>Slack Bolt in Socket Mode to listen for messages, mentions, button clicks, and threaded replies</p></li><li><p>Flask to handle OAuth callbacks and health checks</p></li></ul><p>When a user asks a question in Slack, the bot follows this flow:</p><p>1. Checks whether the Slack user has a valid Databricks OBO token</p><p>2. Routes the question to the correct Genie space</p><p>3. Looks for a semantically similar cached response</p><p>4. Validates cached answers against Delta table versions</p><p>5. Sends the question to Genie on cache miss</p><p>6. Formats the response in Slack with SQL, tables, and suggested follow-ups</p><p>7. Writes cache metadata and audit logs to Delta</p><h1>Prerequisites</h1><p>1. Databricks workspace</p><ul><li><p>Active Databricks workspace</p></li><li><p>One or more Genie spaces</p></li><li><p>SQL Warehouse</p></li><li><p>Unity Catalog permissions</p></li><li><p>Vector Search endpoint and index</p></li></ul><p>2. Slack App</p><ul><li><p>Slack workspace with admin access</p></li><li><p>Slack app with Socket Mode enabled</p></li><li><p>Bot token and app-level token</p></li></ul><p>3. OAuth App</p><ul><li><p>Databricks OAuth application configured for User-to-Machine authentication</p></li><li><p>Redirect URI pointing to the Databricks App callback endpoint</p></li></ul><p>4. Download the application code and make the necessary updates to the config values in app.yaml. Download all files from repo: <a href="https://github.com/adgitdemo/ad_databricks/tree/main/multi-genie-multi-slack-channel-app">https://github.com/adgitdemo/ad_databricks/tree/main/multi-genie-multi-slack-channel-app</a></p><h1>Setup Instructions</h1><h2>1. Slack App Setup</h2><ul><li><p>Create a Slack app and enable Socket Mode.</p></li><li><p>Under OAuth &amp; Permissions, add these bot token scopes:</p><ul><li><p>app_mentions:read</p></li><li><p>chat:write</p></li><li><p>im:history</p></li><li><p>im:read</p></li><li><p>im:write</p></li><li><p>channels:history</p></li><li><p>groups:history</p></li><li><p>users:read</p></li></ul></li><li><p>Subscribe to these bot events:</p><ul><li><p>app_mention</p></li><li><p>message.im</p></li><li><p>message.channels</p></li><li><p>message.groups</p></li></ul></li></ul><ul><li><p>Install the app to your workspace and save:</p><ul><li><p>Bot User OAuth Token</p></li><li><p>App-Level Token</p></li><li><p>Signing Secret</p></li></ul></li></ul><h2>2. Databricks OAuth Setup</h2><ul><li><p>Create a Databricks OAuth application for User-to-Machine authentication.</p></li><li><p>Configure the redirect URI to point to the Flask callback route exposed by the Databricks App.</p></li></ul><blockquote><p>The bot uses OAuth 2.0 with PKCE to securely link each Slack user to their Databricks identity. After authentication, the user&#8217;s pending question is automatically processed, so they do not need to re-type it.</p></blockquote><h2>3. Databricks Workspace Setup</h2><ul><li><p>Prepare the required Databricks resources:</p><ul><li><p>Genie spaces</p></li><li><p>SQL Warehouse</p></li><li><p>Cache Delta table</p></li><li><p>Audit Delta table</p></li><li><p>Vector Search endpoint</p></li><li><p>Vector Search index</p></li><li><p>Catalog and schema permissions</p></li></ul></li></ul><ul><li><p>Permission Grants Summary</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kjO1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kjO1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png 424w, https://substackcdn.com/image/fetch/$s_!kjO1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png 848w, https://substackcdn.com/image/fetch/$s_!kjO1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png 1272w, https://substackcdn.com/image/fetch/$s_!kjO1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!kjO1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png" width="714" height="289" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:289,&quot;width&quot;:714,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:42119,&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://www.databricksters.com/i/196065016?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.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_!kjO1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png 424w, https://substackcdn.com/image/fetch/$s_!kjO1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png 848w, https://substackcdn.com/image/fetch/$s_!kjO1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png 1272w, https://substackcdn.com/image/fetch/$s_!kjO1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1dcfacee-51fb-4db4-974d-ed5726c4ce5e_714x289.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>4. Configure the Application</h2><ul><li><p>Update the application configuration with:</p><ul><li><p>Slack bot token</p></li><li><p>Slack app token</p></li><li><p>Slack signing secret</p></li><li><p>Databricks workspace URL</p></li><li><p>OAuth client ID</p></li><li><p>OAuth redirect URI</p></li><li><p>Genie space mappings</p></li><li><p>Default Genie space alias</p></li><li><p>SQL Warehouse ID</p></li><li><p>Cache table name</p></li><li><p>Audit table name</p></li><li><p>Vector Search endpoint and index</p></li><li><p>Cache similarity threshold</p></li><li><p>Optional Service Principal fallback flag</p></li></ul></li></ul><ul><li><p>Example routing configuration:</p></li></ul><blockquote><p>{</p><p>  &#8220;trips-data&#8221;: {</p><p>    &#8220;space_id&#8221;: &#8220;your-trips-genie-space-id&#8221;,</p><p>    &#8220;description&#8221;: &#8220;Questions about trips, pickup locations, fares, and distance&#8221;</p><p>  },</p><p>  &#8220;finance&#8221;: {</p><p>    &#8220;space_id&#8221;: &#8220;your-finance-genie-space-id&#8221;,</p><p>    &#8220;description&#8221;: &#8220;Questions about revenue, spend, forecasts, and financial KPIs&#8221;</p><p>  }</p><p>}</p></blockquote><h1>Usage</h1><h2>In Slack</h2><h3>Direct Messages:</h3><p>Send a DM to your bot</p><p>Ask questions like: &#8220;Show me sales data for last month&#8221;</p><h3>Channel Mentions:</h3><p>Invite the bot to a channel: /invite @YourBotName</p><p>Mention the bot: @YourBotName what are the top 10 customers?</p><h3>Threaded Conversations:</h3><p>Continue asking questions in a thread</p><p>The bot maintains conversation context within threads</p><h1>Demo Time</h1><p>In the General Slack Channel, the bot will use the default Genie Space to answer questions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!I51n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!I51n!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png 424w, https://substackcdn.com/image/fetch/$s_!I51n!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png 848w, https://substackcdn.com/image/fetch/$s_!I51n!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png 1272w, https://substackcdn.com/image/fetch/$s_!I51n!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!I51n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png" width="1385" height="556" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:556,&quot;width&quot;:1385,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!I51n!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png 424w, https://substackcdn.com/image/fetch/$s_!I51n!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png 848w, https://substackcdn.com/image/fetch/$s_!I51n!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png 1272w, https://substackcdn.com/image/fetch/$s_!I51n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ad4add-ebf6-4f46-af8a-06c7b1f8f004_1385x556.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>DM Thread, the bot will use the default Genie Space to answer questions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YUbT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YUbT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png 424w, https://substackcdn.com/image/fetch/$s_!YUbT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png 848w, https://substackcdn.com/image/fetch/$s_!YUbT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png 1272w, https://substackcdn.com/image/fetch/$s_!YUbT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!YUbT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png" width="992" height="924" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:924,&quot;width&quot;:992,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!YUbT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png 424w, https://substackcdn.com/image/fetch/$s_!YUbT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png 848w, https://substackcdn.com/image/fetch/$s_!YUbT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png 1272w, https://substackcdn.com/image/fetch/$s_!YUbT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c6a3b38-6010-4d03-9418-d59b735d71a6_992x924.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>In a dedicated Trip Slack Channel, the bot will use the Trip Genie Space to answer questions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rglc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rglc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png 424w, https://substackcdn.com/image/fetch/$s_!rglc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png 848w, https://substackcdn.com/image/fetch/$s_!rglc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png 1272w, https://substackcdn.com/image/fetch/$s_!rglc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!rglc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png" width="1386" height="858" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:858,&quot;width&quot;:1386,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!rglc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png 424w, https://substackcdn.com/image/fetch/$s_!rglc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png 848w, https://substackcdn.com/image/fetch/$s_!rglc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png 1272w, https://substackcdn.com/image/fetch/$s_!rglc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84e1c3ab-ea35-4c4f-9310-70cd7914ed0c_1386x858.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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>In a dedicated weather Slack Channel, the bot will use the Weather Genie Space to answer questions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WUuT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WUuT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png 424w, https://substackcdn.com/image/fetch/$s_!WUuT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png 848w, https://substackcdn.com/image/fetch/$s_!WUuT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png 1272w, https://substackcdn.com/image/fetch/$s_!WUuT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!WUuT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png" width="1399" height="596" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6f02a528-ad26-451c-a262-616c7677f209_1399x596.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:596,&quot;width&quot;:1399,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&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_!WUuT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png 424w, https://substackcdn.com/image/fetch/$s_!WUuT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png 848w, https://substackcdn.com/image/fetch/$s_!WUuT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png 1272w, https://substackcdn.com/image/fetch/$s_!WUuT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f02a528-ad26-451c-a262-616c7677f209_1399x596.png 1456w" sizes="auto, 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 buttonBase-GK1x3M"><svg aria-hidden="true" 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" class="icon-noB79L"><g><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 buttonBase-GK1x3M"><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 icon-noB79L"><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="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.databricksters.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 Databricksters! 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>