<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Krishna Tangudu</title>
    <description>The latest articles on DEV Community by Krishna Tangudu (@swaroop_krishna_e2f4b83b2).</description>
    <link>https://dev.to/swaroop_krishna_e2f4b83b2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3814074%2F3d7f150a-da32-48c6-9fda-dd6f74b170e2.jpeg</url>
      <title>DEV Community: Krishna Tangudu</title>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/swaroop_krishna_e2f4b83b2"/>
    <language>en</language>
    <item>
      <title>My Snowflake Agent Was Wrong. So Was My Evaluation.</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Mon, 28 Sep 2026 03:00:59 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/my-snowflake-agent-was-wrong-so-was-my-evaluation-1b46</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/my-snowflake-agent-was-wrong-so-was-my-evaluation-1b46</guid>
      <description>&lt;p&gt;&lt;em&gt;What production conversations taught me about fixing the right layer—and checking whether the fix actually worked.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When an agent gets a disappointing evaluation score, I now ask three questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did it answer the question correctly?&lt;/li&gt;
&lt;li&gt;Did it take an appropriate path to the answer?&lt;/li&gt;
&lt;li&gt;Was the evaluation measuring the behavior I actually wanted?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In my Snowflake agent, those answers did not always agree.&lt;/strong&gt; A revised lookup recovered an object but still received partial tool-selection credit. Other test expectations omitted steps my instructions required. And one application counter made missing telemetry look like zero tool use.&lt;/p&gt;

&lt;p&gt;I was maintaining three things at once: the agent, the evidence about its behavior, and the tests judging it. Changing the prompt was only one possible fix.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;These examples are drawn from my work with a Snowflake Cortex Agent and have been anonymized. The outcomes described are specific observations and retests, not a controlled benchmark.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix lived between the agent and its tool
&lt;/h2&gt;

&lt;p&gt;My agent said an object did not exist. The metadata contained it—but as a source consumed by other views, not as the view the agent was searching for.&lt;/p&gt;

&lt;p&gt;I added a fallback instruction. The lookup still failed.&lt;/p&gt;

&lt;p&gt;The investigation then suggested the semantic tool could not search by source. Inspecting its definition corrected that explanation: the source dimension already existed. Its SQL-generation guidance emphasized view-name searches.&lt;/p&gt;

&lt;p&gt;The successful revision changed both layers. The agent received a fallback telling it when to ask &lt;em&gt;which views consume this source?&lt;/em&gt; The semantic view received guidance explaining that lookup to Cortex Analyst. The recorded retest recovered the object and its consumers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2j03dr0jgm00uzv9y61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2j03dr0jgm00uzv9y61.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An anonymized reconstruction. The source dimension already existed; the change clarified its use.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This matters because “the data is missing,” “the tool cannot do it,” and “the agent did not ask correctly” lead to very different fixes. The investigation briefly entertained each explanation. Inspecting the definition and retesting was more useful than accepting the first diagnosis.&lt;/p&gt;

&lt;p&gt;Even the successful retest had a boundary. Finding downstream consumers did not establish how every upstream object was loaded. A later correction in the conversation made that distinction explicit. A lineage lookup should not turn into an unsupported explanation of the ingestion architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The evidence I actually used
&lt;/h2&gt;

&lt;p&gt;I read real questions, responses, and follow-ups alongside native execution traces. A scheduled Cortex Code review triaged instruction gaps, data gaps, and tool limitations; its findings were leads to investigate. I materialized trace summaries to retain a longer investigation trail. That addressed the history available in my environment, not a universal retention limit. &lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-monitor" rel="noopener noreferrer"&gt;Snowflake monitoring documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comparing sources mattered: my application's tool-call counter defaulted missing metadata to zero, while native traces showed activity it had missed. &lt;strong&gt;A missing measurement had been made to look like a measured zero.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I judge an evaluation
&lt;/h2&gt;

&lt;p&gt;“What was the score?” needs a second question: &lt;em&gt;the score for what?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Snowflake separates several checks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What it judges&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Answer correctness&lt;/td&gt;
&lt;td&gt;An LLM judges the answer against expected content.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool selection accuracy&lt;/td&gt;
&lt;td&gt;Deterministic matching of expected and actual tool names and call counts; order is ignored.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool execution accuracy&lt;/td&gt;
&lt;td&gt;Expected inputs and outputs are compared with matching tool invocations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logical consistency&lt;/td&gt;
&lt;td&gt;An LLM checks consistency across instructions, planning, and actions without reference answers.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tool selection can penalize extra calls even when the answer improves. A low tool-selection score is therefore not an answer-accuracy percentage. The mechanics are in the appendix and &lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-evaluations" rel="noopener noreferrer"&gt;Snowflake’s evaluation documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That distinction explained some of my confusing results. Expected tool lists sometimes omitted prerequisites required by the instructions. Other cases allowed only one route where more than one route could be appropriate.&lt;/p&gt;

&lt;p&gt;But I should not make a test easier just because the agent failed it. Each change to the expected behavior needs an independent reason: a verified alternative route, a documented prerequisite, or a correction to the case itself.&lt;/p&gt;

&lt;p&gt;The missing-object case illustrates why I inspect individual records. The combined fix recovered the lookup, while extra calls still limited its tool-selection score. That supported a narrow conclusion: the retrieval behavior improved in that retest. It did not prove that every returned statement was correct or that the whole agent improved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real questions are good test inputs. Old answers are not automatically ground truth.
&lt;/h3&gt;

&lt;p&gt;Real conversations supply questions I would not invent in a demo. Turning them into tests also creates traps. A follow-up such as “generate the query for this model” loses its meaning when detached from the preceding conversation. A previously successful API response may contain a wrong answer. A time-sensitive answer can become stale.&lt;/p&gt;

&lt;p&gt;For a useful regression case, I need the question's context, independently checked expectations, acceptable uncertainty, and the behavior that must not recur. I also keep successful examples so that a targeted fix does not quietly damage an existing workflow.&lt;/p&gt;

&lt;p&gt;Changing the questions or expected answers creates a new test baseline. Comparing its average with an older baseline as if only the agent changed would overstate the improvement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Did the test exercise the capability?
&lt;/h3&gt;

&lt;p&gt;The traces also challenged what I thought my tests covered. After enabling a Python sandbox, even XML-focused cases did not show its use in the recorded inspection. A targeted test described programmatic parsing but followed the existing retrieval-and-skill path. &lt;strong&gt;Configured, mentioned in planning, and invoked are different states.&lt;/strong&gt; A correct answer through another route could pass an answer test while leaving the sandbox untested. I needed evidence of invocation and correct extraction before attributing an improvement to Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right answer can still be the wrong interaction
&lt;/h2&gt;

&lt;p&gt;Another incident involved an object name with words in the wrong order. The agent found a plausible alternative and began analysing it. The user had to correct the selection.&lt;/p&gt;

&lt;p&gt;I introduced similar-name search and confirmation. Tool checks showed that candidate retrieval worked. Then I tested through the application.&lt;/p&gt;

&lt;p&gt;The revised agent found the intended candidate—and continued into analysis without asking me to confirm it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval had improved. The interaction still violated the requirement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs222hnyph3jxfhbeu1rn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs222hnyph3jxfhbeu1rn.gif" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Illustrative animation. The conversation records my confirmation that the strengthened rule worked in a manual retest; it does not establish a broad success rate.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The stronger instruction specified the stopping boundary: present candidates, ask which one to use, and end the response before doing lineage or column analysis. It also included examples of the unwanted and intended behavior.&lt;/p&gt;

&lt;p&gt;For this case, my proposed regression checks are concrete:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Expected behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exact object exists&lt;/td&gt;
&lt;td&gt;Analyse that object.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact object is absent; alternatives exist&lt;/td&gt;
&lt;td&gt;Present candidates and ask; do not analyse a substitute yet.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No candidates exist&lt;/td&gt;
&lt;td&gt;Explain the search boundary without inventing an object.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User confirms a candidate&lt;/td&gt;
&lt;td&gt;Continue with the confirmed object.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is a synthetic test pattern a reader can adapt. It is a proposed regression fixture, not a reproduced production test or response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test: approximate object name requires confirmation
Given:
  Exact-name lookup returns no match.
  Candidate lookup returns synthetic objects A and B.
Before: the agent chooses a candidate and begins analysis.
Required after:
  Present A and B with distinguishing context.
  Ask the user to choose, then end the turn.
Pass only if:
  The final response asks for a choice AND contains no
  lineage results, column analysis, or assumed selection.
Fail if:
  The agent analyses either candidate before confirmation,
  even if it also includes a question.
Next turn:
  User selects B; analysis must refer to B, not A.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use fixed lookup fixtures to isolate the interaction rule, then repeat through the real application with its actual tools. A question mark alone is not a pass. Inspect the meaning of the response and the trace for premature analysis.&lt;/p&gt;

&lt;p&gt;A final-answer similarity score alone would miss part of that contract. I need to check the turn where the agent was supposed to stop.&lt;/p&gt;

&lt;p&gt;There was a deeper reason to care about that pause. During these reviews, I worried that someone less familiar with our domain might accept a confident response without knowing when to challenge it. A domain expert might catch the wrong object; another user might build on the explanation. That concern was not a measured comparison between user groups. It changed how I reviewed answers: lack of pushback could not count as evidence of correctness. Asking for confirmation exposes a choice the agent would otherwise make silently, though confirmation alone does not verify the analysis that follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Each revision should explain its reason
&lt;/h2&gt;

&lt;p&gt;My changes were not all additions to the main prompt. They addressed different layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Layer to investigate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wrong object selected without confirmation&lt;/td&gt;
&lt;td&gt;Agent interaction instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata exists but the lookup asks the wrong question&lt;/td&gt;
&lt;td&gt;Agent routing and semantic-view guidance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Specialist parsing or traversal is incomplete&lt;/td&gt;
&lt;td&gt;Domain skill and source coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needed computation is unavailable&lt;/td&gt;
&lt;td&gt;Tool capability, followed by invocation tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generated output cannot be accessed in the application&lt;/td&gt;
&lt;td&gt;Delivery channel and response format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plausible answer receives unexpected evaluation penalties&lt;/td&gt;
&lt;td&gt;Expected behavior and per-case scoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool use appears absent in one dashboard&lt;/td&gt;
&lt;td&gt;Instrumentation and native trace evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A deployment mistake changed my rule.&lt;/strong&gt; The agent was recreated during this work, resetting its native version history while our conversation still used the old labels. I explicitly said not to recreate it again without asking me. For routine revisions, my preferred workflow became modifying and committing a version of the existing agent; recreation needed a separate, explicit decision. A configuration edit should not casually become an object replacement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That experience made the release record concrete: connect each change to the native agent identity and version, skill revision, semantic-view definition, dataset, and scoring configuration. A conversation label cannot substitute for that record. The appendix shows selected version reasons.&lt;/p&gt;

&lt;p&gt;None of this happened in a quiet, finished post-mortem. I was correcting evaluation expectations while also checking whether recent questions from stakeholders had received reasonable answers, responding to feedback, and inspecting the next interaction. The service remained in use while I was learning how to evaluate it. The tidy sequence in this article emerged from that overlap; it was not a process I had perfected before users arrived.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do first on the next agent
&lt;/h2&gt;

&lt;p&gt;I did not begin with a formal AI lifecycle. I began with people correcting the agent.&lt;/p&gt;

&lt;p&gt;That grew into a repeatable engineering practice: retain the relevant evidence, investigate the failure, change the appropriate layer, and test the behavior again. Keep observations separate from hypotheses. Keep a successful component test separate from an application retest. Keep a better evaluation score separate from a better answer.&lt;/p&gt;

&lt;p&gt;If I were starting again, I would write the regression case before the fix:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should the agent do differently when someone asks this again—and what evidence would convince me it did?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question has been more useful than asking whether the agent is finally “good.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: the details behind the story
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Evaluation mechanics
&lt;/h3&gt;

&lt;p&gt;For tool selection, the documented formula is matched calls divided by the larger of expected entries or actual calls. An &lt;strong&gt;illustrative&lt;/strong&gt;, invented example: one expected call and four actual calls, with one match, scores 0.25. That does not mean the answer is 25% correct. Tool execution handles extra calls differently. Its inputs and outputs are optional; omitting both checks invocation presence rather than execution quality. Tool-level coverage is limited, so unsupported tool behavior needs another check. &lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-evaluations" rel="noopener noreferrer"&gt;Cortex Agent evaluations&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Retaining an investigation trail
&lt;/h3&gt;

&lt;p&gt;I materialized trace summaries on a schedule to extend the investigation window, with known limitations around join precision and late-arriving spans.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdwlncyque0k6kq57b4b1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdwlncyque0k6kq57b4b1.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Selected version reasons
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcfjumrfkfe4nu78lvr85.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcfjumrfkfe4nu78lvr85.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Selected native versions and their reasons. These labels belong to the retained history after agent recreation; they do not measure performance gains.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>ai</category>
      <category>testing</category>
      <category>agents</category>
    </item>
    <item>
      <title>Natural Language Is the Interface, Not the Semantic Layer</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Tue, 15 Sep 2026 18:48:24 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/natural-language-is-the-interface-not-the-semantic-layer-ce9</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/natural-language-is-the-interface-not-the-semantic-layer-ce9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is Part 2 of a two-part technical series. &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/moving-data-is-easier-than-moving-knowledge-2co4"&gt;Part 1&lt;/a&gt; showed how an Enterprise Data Discovery Assistant recovers legacy logic and produces grounded SQL. &lt;a href="https://medium.com/@ghrupakkumar/how-to-use-ai-agents-to-build-ai-ready-data-products-c19ea4895102" rel="noopener noreferrer"&gt;Rupak's article on AI-ready data products&lt;/a&gt; provides the larger context: agents need machine-readable semantics, active contracts, and engineering guardrails. This post follows the next step—from a business question to governed execution and an explained answer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;All names, identifiers, queries, and results below are synthetic. Quantities are rounded to communicate scale without publishing an internal inventory.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Natural language does not remove the need for data modeling. It makes good data modeling visible to more people.&lt;/p&gt;

&lt;p&gt;Our preview tests asked whether business users could explore an enterprise-scale Customer 360 domain—more than 100 million records across multiple curated tables—without first learning schemas, joins, or SQL.&lt;/p&gt;

&lt;p&gt;The answer was encouraging, with an important qualification: &lt;strong&gt;natural language is the interface, not the semantic layer&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One question hides several decisions
&lt;/h2&gt;

&lt;p&gt;Consider a straightforward request:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which customer segments had the highest order value last quarter, and how did that change from the previous quarter?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before SQL can run, the system must resolve what &lt;em&gt;order value&lt;/em&gt; means, which field represents &lt;em&gt;customer segment&lt;/em&gt;, how orders relate to customers, which calendar defines &lt;em&gt;last quarter&lt;/em&gt;, whether the user is allowed to see the requested data—and whether the data product is trustworthy right now.&lt;/p&gt;

&lt;p&gt;A language model looking only at table and column names may guess. A semantic view makes the analytical decisions explicit: grain, metrics, time semantics, filters, and supported relationships. Snowflake policies determine who may access the result, while our runtime contract check determines whether the data product is fit to use now.&lt;/p&gt;

&lt;p&gt;It defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Logical tables, row grain, primary keys, and unique keys&lt;/strong&gt;, so the meaning of one row is explicit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions and time dimensions&lt;/strong&gt;, such as customer segment and fiscal quarter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facts and governed metrics&lt;/strong&gt;, including aggregation and distinct-count behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationships&lt;/strong&gt;, including bridge tables for many-to-many associations, so join paths are modeled rather than improvised.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filters, synonyms, and question-handling instructions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verified queries&lt;/strong&gt;, which pair important question patterns with reviewed SQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://docs.snowflake.com/en/user-guide/views-semantic/semantic-view-yaml-spec" rel="noopener noreferrer"&gt;Snowflake recommends semantic views for new Cortex Analyst implementations&lt;/a&gt;. They are schema-level objects integrated with Snowflake privileges and metadata—not prompt text pretending to be governance.&lt;/p&gt;

&lt;p&gt;A semantic view tells the assistant &lt;strong&gt;what the data means&lt;/strong&gt;. A data contract tells it &lt;strong&gt;whether that data product is fit to use now&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For governed business questions in our preview, the assistant performs a mandatory contract check before using the analytical semantic view. It evaluates the target domain's contract health, freshness, latest validation result, and any failure reason.&lt;/p&gt;

&lt;p&gt;The contract check is application logic around the agent; it is not performed automatically by the semantic view itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  From question to governed answer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvksh3cbvn9exh0ypjacy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvksh3cbvn9exh0ypjacy.png" alt=" " width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow is deliberately simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;Enterprise Data Assistant&lt;/strong&gt; identifies the user's intent and target business domain.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;data-contract check&lt;/strong&gt; evaluates contract status, freshness, quality, and the latest validation outcome before the business query runs.&lt;/li&gt;
&lt;li&gt;The contract result determines the response path: proceed normally when trustworthy, or attach the appropriate warning when the data is degraded, stale, inactive, or failed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cortex Analyst&lt;/strong&gt; interprets the question using the selected semantic view, which supplies the governed dimensions, metrics, relationships, filters, and examples needed to generate SQL.&lt;/li&gt;
&lt;li&gt;The SQL runs inside the governed data platform. Snowflake privileges and data-protection policies still apply.&lt;/li&gt;
&lt;li&gt;The response returns the result together with the SQL, definitions, scope, and any required warning.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The distinction matters. When the contract is active, fresh, and passing, the check stays behind the scenes and the user gets a clean answer. A stale or degraded contract adds a caution. An inactive or failed contract produces a prominent warning that the result is not guaranteed before the preview attempts the query. For a higher-risk domain, the same decision point can be implemented as a hard stop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents" rel="noopener noreferrer"&gt;Cortex Agents can use Cortex Analyst for structured data and route across multiple semantic views&lt;/a&gt;. Cortex Analyst first attempts semantic SQL, where metrics, dimensions, and relationships come from the semantic view. When the modeled coverage cannot satisfy a request, Routing Mode can fall back to standard SQL on physical tables. That flexibility is useful, but for governed metrics the fallback should be treated as a different confidence path: expose it for review, request clarification, or block it for higher-risk questions rather than imply that it carries the same semantic guarantees. &lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst/cortex-analyst-routing-mode" rel="noopener noreferrer"&gt;Snowflake documents this behavior as Routing Mode&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The answer should show its work
&lt;/h2&gt;

&lt;p&gt;The experience should still feel conversational. The difference is that the answer exposes enough evidence for a user or engineer to challenge it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7sum0fj20momh56ew3n.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7sum0fj20momh56ew3n.gif" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Synthetic demonstration: no production interface or customer data is shown.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the example above, the response contract is more important than the visual polish. It should make six things clear:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the user sees&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A concise answer&lt;/td&gt;
&lt;td&gt;The user gets the result without reading SQL first.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metric and dimension definitions&lt;/td&gt;
&lt;td&gt;Business terms are not left open to interpretation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time range and filters&lt;/td&gt;
&lt;td&gt;The scope of the answer is explicit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract warning, when relevant&lt;/td&gt;
&lt;td&gt;Users know when freshness or quality may affect trust.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generated SQL&lt;/td&gt;
&lt;td&gt;An engineer can inspect and reproduce the query.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warnings or clarification&lt;/td&gt;
&lt;td&gt;Ambiguity is surfaced instead of silently resolved.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is a more useful standard than “the chatbot returned an answer.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy is an engineering loop
&lt;/h2&gt;

&lt;p&gt;Semantic metadata improves grounding, but it does not prove that every generated query is correct.&lt;/p&gt;

&lt;p&gt;Important question patterns should be captured in a &lt;strong&gt;Verified Query Repository&lt;/strong&gt;: a natural-language question paired with SQL whose logic and result have been validated by a qualified reviewer. The SQL should use the logical tables and columns defined by the semantic view rather than bypassing them for physical objects. Cortex Analyst can use relevant verified queries to guide similar requests. A verified example is guidance—not a blanket certification of every future answer. &lt;a href="https://docs.snowflake.com/en/user-guide/views-semantic/verified-query-repository" rel="noopener noreferrer"&gt;Snowflake's Verified Query Repository documentation&lt;/a&gt; makes that distinction concrete.&lt;/p&gt;

&lt;p&gt;The next layer is repeatable evaluation. Current Cortex Analyst evaluations compare generated SQL results with selected verified queries, track regressions, and record latency. This turns semantic-layer tuning into a measurable build-test-run-improve cycle instead of a collection of impressive demos. &lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst-evaluations" rel="noopener noreferrer"&gt;Snowflake documents the evaluation workflow here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We also learned to compare results, not only SQL. Reconciliation must use the same as-of timestamp, fiscal calendar, currency logic, exclusion rules, and entity scope. Two syntactically valid queries can still answer different business questions.&lt;/p&gt;

&lt;p&gt;Our practical checklist is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with a narrow, coherent business domain.&lt;/li&gt;
&lt;li&gt;Define grain, keys, relationship cardinality, metrics, filters, and synonyms explicitly.&lt;/li&gt;
&lt;li&gt;Add reviewed queries for high-value and high-risk questions.&lt;/li&gt;
&lt;li&gt;Test ambiguous wording, invalid requests, boundary cases, and every contract state—not only the happy path.&lt;/li&gt;
&lt;li&gt;Track accuracy, regression, latency, and workload cost with representative questions.&lt;/li&gt;
&lt;li&gt;Show the generated SQL and scope when the audience needs traceability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Governance does not disappear behind chat
&lt;/h2&gt;

&lt;p&gt;A conversational interface must not become a shortcut around access controls.&lt;/p&gt;

&lt;p&gt;Access control and data contracts answer different questions. Privileges determine &lt;strong&gt;whether this user may access the data&lt;/strong&gt;. The contract check determines &lt;strong&gt;whether the selected data product should be trusted in its current state&lt;/strong&gt;. A governed assistant needs both decisions; neither replaces the other.&lt;/p&gt;

&lt;p&gt;Semantic views participate in Snowflake's privilege model. Row-access and masking policies applied to underlying tables can propagate to the semantic view and remain enforced. One subtle but important caution from Snowflake's guidance is that sample values stored as semantic metadata are not masked, so sensitive examples should not be embedded there. &lt;a href="https://docs.snowflake.com/en/user-guide/views-semantic/best-practices-dev" rel="noopener noreferrer"&gt;See Snowflake's development and deployment guidance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are product boundaries too. Cortex Analyst is designed for questions that can be resolved with SQL; it is not automatically a general business-insight engine. It also cannot refer to the results of a previous SQL query as if it had retained that result set. Those limits should shape both the user experience and the test suite. &lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst" rel="noopener noreferrer"&gt;The current limitations are documented here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed between Part 1 and Part 2
&lt;/h2&gt;

&lt;p&gt;Part 1 recovered the knowledge needed to rebuild a data product: joins, filters, calculations, ownership, and dependencies.&lt;/p&gt;

&lt;p&gt;Part 2 makes approved knowledge queryable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Recovered legacy knowledge
          ↓
Governed data product + semantic view + contract
          ↓
Natural-language question
          ↓
Data-contract decision
          ↓
Semantic grounding + governed SQL
          ↓
Explained result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The language model is useful at the top of this stack because the hard decisions are represented beneath it.&lt;/p&gt;

&lt;p&gt;That is the larger lesson from the preview. Natural-language analytics is not a replacement for engineering discipline. Done well, it is a new interface to that discipline—one that lets more people ask useful questions while keeping definitions, access, SQL, and validation visible.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>snowflake</category>
      <category>dataengineering</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Mirroring Snowflake Iceberg into Microsoft Fabric : The Gotchas - Part 2:</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 10 Sep 2026 17:32:05 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/snowflake-iceberg-fabric-what-we-tried-what-support-confirmed-and-what-comes-next-ie0</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/snowflake-iceberg-fabric-what-we-tried-what-support-confirmed-and-what-comes-next-ie0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Previously:&lt;/strong&gt; In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/what-i-learned-running-snowflake-iceberg-mirroring-in-microsoft-fabric-and-debugging-real-1151"&gt;Mirroring Snowflake Iceberg into Microsoft Fabric: The Gotchas&lt;/a&gt;, I documented connection issues, permissions, and the difference between healthy mirroring and a usable SQL analytics endpoint.&lt;/p&gt;

&lt;p&gt;The goal remains the same: give analysts a shared serving layer in Fabric without making every downstream workflow query Snowflake independently.&lt;/p&gt;

&lt;p&gt;Since that post, two support investigations have added to the picture. One concerns the cost of continuous mirroring when the source changes only a few times daily. The other reverses an earlier troubleshooting pattern: the SQL analytics endpoint reads the data, but Spark fails.&lt;/p&gt;

&lt;p&gt;This is the checklist of what we have learned, which workarounds we have used, and the options we plan to evaluate next. &lt;strong&gt;Checked items describe experience or completed investigation. Unchecked items are future tests, not claimed results.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What we have done so far
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[x] &lt;strong&gt;Established the Snowflake–Fabric integration.&lt;/strong&gt; The first post covers the connection and permission fixes, including separate access to Iceberg storage.&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;Separated replication health from query behavior.&lt;/strong&gt; We validate actual results through the consuming engine, rather than treating a healthy status as sufficient.&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;Compared SQL analytics endpoint and Spark behavior on the decimal issue.&lt;/strong&gt; SQL succeeded where the affected Spark read failed.&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;Validated the source, table, and file schemas.&lt;/strong&gt; We submitted those findings to Microsoft support.&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;Tested the non-vectorized Spark workaround.&lt;/strong&gt; Reads succeeded; we subsequently reported performance and capacity impact.&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;Investigated mirroring schedules, restart behavior, and Delta CDF.&lt;/strong&gt; Support confirmed the limitations described below.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Measure whether Snowflake AWS-to-Azure replication improves the total economics.&lt;/strong&gt; This is our next architecture experiment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Continuous mirroring: the business clock matters
&lt;/h2&gt;

&lt;p&gt;Our source data changes approximately three times per day. The mirroring support case described a Snowflake cloud Layer cost spike and asked a practical question: can we run replication around those updates instead of continuously?&lt;/p&gt;

&lt;p&gt;At roughly 2,500 tables across the estate, maintaining an individual pipeline for every table would also be a substantial burden. That concern is about operational scale; it does not mean every batch design requires a separate pipeline per table.&lt;/p&gt;

&lt;p&gt;What the investigation established:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[x] &lt;strong&gt;Scheduling:&lt;/strong&gt; Microsoft documents no configurable mirroring schedules or replication windows today. &lt;a href="https://learn.microsoft.com/en-us/fabric/mirroring/snowflake#cost-optimization-recommendations" rel="noopener noreferrer"&gt;Snowflake mirroring guidance&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;Stop/start behavior:&lt;/strong&gt; support confirmed a full reload rather than continuation from the previous CDC position, consistent with the public FAQ. A timer around stop/start is therefore not equivalent to a scheduled incremental load. &lt;a href="https://learn.microsoft.com/en-us/fabric/mirroring/snowflake-mirroring-faq" rel="noopener noreferrer"&gt;Snowflake mirroring FAQ&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;Disabling the service user:&lt;/strong&gt; support explained that this is not a supported scheduling mechanism; connection retries and recovery become part of the problem.&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;Alternative suggested:&lt;/strong&gt; support recommended evaluating a Copy activity or Copy job, while acknowledging that this requires architectural work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The case is closed, but the correspondence acknowledges a scheduling limitation. It does not record delivery of a scheduling fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The decimal issue: SQL worked, Spark did not
&lt;/h2&gt;

&lt;p&gt;The second case captured this error, with the column anonymized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parquet column cannot be converted ...
Column: [AMOUNT], Expected: decimal(15,2), Found: INT32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our diagnostics showed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Reported representation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Snowflake source column&lt;/td&gt;
&lt;td&gt;NUMBER(15,2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fabric table schema&lt;/td&gt;
&lt;td&gt;decimal(15,2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Examined Parquet file&lt;/td&gt;
&lt;td&gt;decimal(9,2), backed by INT32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL analytics endpoint&lt;/td&gt;
&lt;td&gt;Query succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Affected Spark read&lt;/td&gt;
&lt;td&gt;Failed with vectorization enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spark with vectorization disabled&lt;/td&gt;
&lt;td&gt;Read succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The logical table schema and a file's physical encoding are different layers. The failing read path did not successfully reconcile the narrower file representation with the declared decimal type. The observed result was a read failure, not evidence of corrupted business values.&lt;/p&gt;

&lt;p&gt;Microsoft's public guidance lists the type-width issue and the Spark workaround. &lt;a href="https://learn.microsoft.com/en-us/fabric/onelake/onelake-iceberg-tables#limitations-and-considerations" rel="noopener noreferrer"&gt;OneLake Iceberg limitations&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[x] &lt;strong&gt;Workaround used:&lt;/strong&gt; disable the vectorized Parquet reader in the affected Spark session.&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;Impact reported:&lt;/strong&gt; performance and capacity concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a scoped illustration of the workaround, using fictional table and column names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fabric PySpark: use a small, known slice of an affected table.
&lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spark.sql.parquet.enableVectorizedReader&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        SELECT order_id, amount
        FROM dbo.orders_iceberg
        WHERE order_id IN (101, 102, 103)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The action runs before the setting is restored. This example illustrates the configuration used; it is not a new benchmark or a reproduction executed for this article. A simple query showed a performance difference from &lt;em&gt;20 seconds to 2 minutes 26 seconds&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The lesson extends my previous post: SQL and Spark both need independent acceptance tests. Either engine can expose a limitation that the other does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Options on our checklist
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Our status&lt;/th&gt;
&lt;th&gt;Why it is relevant&lt;/th&gt;
&lt;th&gt;What still needs proof&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Existing Snowflake mirroring / Iceberg integration&lt;/td&gt;
&lt;td&gt;Used; support cases investigated&lt;/td&gt;
&lt;td&gt;Shared Fabric serving layer&lt;/td&gt;
&lt;td&gt;Sustainable cost and consistent engine behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL analytics endpoint for affected reads&lt;/td&gt;
&lt;td&gt;Worked in the reported case&lt;/td&gt;
&lt;td&gt;Keeps compatible SQL workloads usable&lt;/td&gt;
&lt;td&gt;Coverage of workloads that currently require Spark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spark non-vectorized reader&lt;/td&gt;
&lt;td&gt;Tested workaround&lt;/td&gt;
&lt;td&gt;Restores affected reads&lt;/td&gt;
&lt;td&gt;Performance and capacity at production scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduled Copy job or reusable Copy pipeline&lt;/td&gt;
&lt;td&gt;Official option; not benchmarked here&lt;/td&gt;
&lt;td&gt;Direct control over movement frequency&lt;/td&gt;
&lt;td&gt;CDC eligibility, deletes, schema changes, cost, and management at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snowflake AWS → Snowflake Azure replication&lt;/td&gt;
&lt;td&gt;Planned next experiment&lt;/td&gt;
&lt;td&gt;Place a controlled replica nearer Azure consumers&lt;/td&gt;
&lt;td&gt;Eligible objects, consumer access, freshness, and total cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct Snowflake access&lt;/td&gt;
&lt;td&gt;Tested workaround&lt;/td&gt;
&lt;td&gt;Keeps users productive&lt;/td&gt;
&lt;td&gt;Source load and total BI/query cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  5. What comes next: Snowflake AWS → Snowflake Azure
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Our hypothesis:&lt;/strong&gt; a scheduled Azure replica may be cheaper to operate when it replaces substantial repeated reads across the cloud boundary. We have not established savings yet.&lt;/p&gt;

&lt;p&gt;The candidate flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Snowflake AWS source
        |
        | scheduled Snowflake replication
        v
Snowflake Azure secondary + target-region storage where required
        |
        | consumer path to validate separately
        v
Fabric Iceberg access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Snowflake officially supports replication across AWS and Azure within an organization. Replication groups provide read-only secondaries and configurable refresh schedules. Database replication is available across editions; failover groups and various account-object capabilities have additional edition requirements. &lt;a href="https://docs.snowflake.com/en/user-guide/account-replication-intro" rel="noopener noreferrer"&gt;Replication overview&lt;/a&gt;, &lt;a href="https://docs.snowflake.com/en/sql-reference/sql/create-replication-group" rel="noopener noreferrer"&gt;Replication schedules&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are evaluating replication for read access. Failover is a separate operating requirement and should not be introduced merely to make a read-only secondary writable for an integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The cost test that will decide the direction
&lt;/h2&gt;

&lt;p&gt;Snowflake replication adds transfer and service-compute charges, billed to the target account, plus target storage costs. Refresh frequency and changed data volume affect the total. The initial seed must be measured separately from steady-state refreshes. &lt;a href="https://docs.snowflake.com/en/user-guide/account-replication-cost" rel="noopener noreferrer"&gt;Snowflake replication cost&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our comparison will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current path:
  source integration activity + transfer/storage requests
  + Fabric serving cost + operating effort

Azure replica path:
  replication transfer + replication compute + target storage
  + Azure-side integration/query cost + Fabric serving cost
  + operating effort
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are measurement categories, not a savings estimate. For external Iceberg storage, include the applicable cloud-storage bill as well as Snowflake and Fabric usage.&lt;/p&gt;

&lt;p&gt;My current direction is to test Azure replication. The former tests whether data location is driving unnecessary cost; the latter tests whether movement frequency is the larger problem. The decimal issue remains its own compatibility test.&lt;/p&gt;

&lt;p&gt;That is what I want the next installment to report: which option we tested, what improved, what it cost, and what still required a workaround.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>azure</category>
      <category>cloud</category>
      <category>database</category>
    </item>
    <item>
      <title>Moving Data Is Easier Than Moving Knowledge</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Mon, 07 Sep 2026 04:15:25 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/moving-data-is-easier-than-moving-knowledge-2co4</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/moving-data-is-easier-than-moving-knowledge-2co4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is Part 1 of a two-part technical series. &lt;a href="https://medium.com/@ghrupakkumar/how-to-use-ai-agents-to-build-ai-ready-data-products-c19ea4895102" rel="noopener noreferrer"&gt;Rupak's article on building AI-ready data products&lt;/a&gt; describes the larger destination: a machine-readable context layer, governed semantics, and engineering guardrails. This post tackles the migration problem that comes first—recovering legacy knowledge and turning it into grounded SQL. Part 2 will cover natural-language questions, governed execution, and explained answers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;All examples and names below are synthetic&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Moving data is often the easier part of an SAP-to-cloud migration. Moving the knowledge is harder.&lt;/p&gt;

&lt;p&gt;A pipeline can copy a table. It does not automatically carry forward why two tables are joined, which filter defines an open order, how technical statuses become business terms, or who owns the report.&lt;/p&gt;

&lt;p&gt;Without that context, a new platform can produce a technically valid answer that is wrong for the business.&lt;/p&gt;

&lt;h2&gt;
  
  
  A question the copied tables cannot answer
&lt;/h2&gt;

&lt;p&gt;Imagine an estate with more than 5,000 SAP HANA calculation views, hundreds of Qlik and Spotfire applications, and more than 2,500 cloud tables.&lt;/p&gt;

&lt;p&gt;A data engineer receives a simple request:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rebuild our sales-order backlog dashboard. What existing logic should I preserve?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is scattered. A HANA calculation view may define the joins, filters, and open quantity. A Qlik application may map technical statuses and write a shared QVD. A Spotfire analysis may implement its own aging logic. Ownership and downstream usage may live in reporting metadata, while field-level dependencies live in a lineage graph.&lt;/p&gt;

&lt;p&gt;Qlik and Spotfire are &lt;strong&gt;parallel reporting platforms over HANA&lt;/strong&gt;. They are not sequential stages feeding one another; the same HANA model may support applications in either or both.&lt;/p&gt;

&lt;p&gt;The migration task is therefore not just code conversion. It is evidence discovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the assistant builds context
&lt;/h2&gt;

&lt;p&gt;We call the user experience an &lt;strong&gt;Enterprise Data Discovery Assistant&lt;/strong&gt;. Under the hood, a bounded Snowflake Cortex Agent selects retrieval and analysis tools. “Agent” describes the architecture; “assistant” describes its read-only responsibility. It does not change source systems, deploy code, or promote pipelines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Engineer question
       |
       v
Enterprise Data Discovery Assistant
       |
       +-- HANA repository code
       +-- Parallel BI artifacts
       |      +-- Qlik scripts and QVDs
       |      +-- Spotfire queries and metadata
       +-- Reporting metadata catalog
       +-- Schema, object, and column lineage
       |
       v
Recovered logic + ownership + dependencies
       |
       v
Grounded SQL draft -&amp;gt; data-engineer validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four evidence layers play different roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HANA repository code&lt;/strong&gt; provides the calculation-view definition. Parsing indexed &lt;code&gt;.hdbcalculationview&lt;/code&gt; XML reveals sources, joins, filters, calculated columns, unions, and nested-view dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parallel BI artifacts&lt;/strong&gt; reveal reporting logic. Qlik contributes load queries, aliases, mappings, resident loads, and QVD flow. Spotfire contributes source queries, columns, and any calculations present in its indexed metadata or exports.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;reporting metadata catalog&lt;/strong&gt; connects Qlik and Spotfire applications to HANA models, queries, fields, owners, paths, saved-query details, and comments. It adds operational context around the code; it does not replace the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schema and lineage metadata&lt;/strong&gt; traces upstream sources, downstream consumers, object dependencies, and column paths, then helps map recovered logic to approved target objects.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these sources answer more than “What SQL should I write?” They can also answer: Who owns this dashboard? Which reports use this HANA view? Which QVDs feed the application? What could break if the model changes?&lt;/p&gt;

&lt;p&gt;Search locates the artifacts. Deterministic parsers extract their structure. The language model relates the findings to the engineer's question.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a question to grounded SQL
&lt;/h2&gt;

&lt;p&gt;The experience should feel like a conversation, not a metadata report. The engineer asks one question; the assistant shows what it checked and returns an evidence-backed answer with SQL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flj15inqa1mx47pv00thg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flj15inqa1mx47pv00thg.gif" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Synthetic chat demonstration: no production data or internal interface is shown.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The response separates recovered facts from the proposed target design. In this example, the assistant verifies the HANA joins and filter, recovers the Qlik status mapping and QVD flow, identifies the owner, and finds downstream Qlik and Spotfire consumers before drafting SQL.&lt;/p&gt;

&lt;p&gt;Only then does the assistant propose a starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ordered_qty&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confirmed_qty&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;open_quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;business_status&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_order_item&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;status_map&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancelled_flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'N'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL is useful because its origin and limitations are visible—not because it appeared quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grounded SQL is a handoff
&lt;/h2&gt;

&lt;p&gt;The draft is not a production pipeline. A data engineer still confirms the grain, compares results with the legacy report, maps the logic to canonical target names, and preserves null, fallback, and refresh behavior. Production work also requires incremental patterns such as CDC or watermarks, data-quality tests, security, documentation, owner approval, and CI/CD which was covered in Developer Toolkit.&lt;/p&gt;

&lt;p&gt;Trust comes from a few explicit boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer exact identifiers when available; use semantic search for discovery, then verify the selected objects.&lt;/li&gt;
&lt;li&gt;Parse large HANA XML and Qlik scripts deterministically.&lt;/li&gt;
&lt;li&gt;Separate &lt;strong&gt;verified evidence&lt;/strong&gt;, &lt;strong&gt;proposed design&lt;/strong&gt;, and &lt;strong&gt;missing evidence&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Keep generated code read-only until it passes normal engineering review and testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an expected Qlik or Spotfire expression is absent from the indexed sources, the assistant says so instead of reconstructing it from assumptions.&lt;/p&gt;

&lt;p&gt;The same human-in-the-loop pattern shaped the assistant itself. &lt;strong&gt;Snowflake CoCo&lt;/strong&gt;,supported the build-and-tune cycle by helping refine tool instructions, test prompts, failure analysis, and response behavior. Engineers decided which changes to adopt and validated the results.&lt;/p&gt;

&lt;p&gt;Moving data gives a new platform rows and columns. Moving knowledge preserves the meaning, ownership, and dependencies people rely on—and provides the context needed to build governed data products.&lt;/p&gt;

&lt;p&gt;Part 2 will follow a natural-language question through governed SQL execution and show how the final answer explains its sources and limitations.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>snowflake</category>
      <category>sap</category>
      <category>agents</category>
    </item>
    <item>
      <title>From Optimization to Protection: Adding a Security and Governance Agent to Your Snowflake Multi-Agent Team (Part 3)</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 09 Jul 2026 21:26:01 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/from-optimization-to-protection-adding-a-security-and-governance-agent-to-your-snowflake-369f</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/from-optimization-to-protection-adding-a-security-and-governance-agent-to-your-snowflake-369f</guid>
      <description>&lt;h1&gt;
  
  
  From Optimization to Protection: Adding a Security and Governance Agent to Your Snowflake Multi-Agent Team (Part 3)
&lt;/h1&gt;

&lt;p&gt;In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/ask-your-snowflake-account-anything-build-an-ai-admin-agent-with-cortex-github-copilot-1mk6"&gt;Part 1&lt;/a&gt;, we built an Admin Agent for usage and cost visibility.&lt;br&gt;
In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/from-one-agent-to-many-building-a-multi-agent-team-for-snowflake-administration-part-2-379d"&gt;Part 2&lt;/a&gt;, we added a Cost Optimizer Agent and an Orchestrator that routes questions to specialists.&lt;/p&gt;

&lt;p&gt;Now we close the loop with the third specialist: a &lt;strong&gt;Security and Governance Agent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This turns your assistant from "what happened" and "what to optimize" into a full team that also answers "what is risky right now".&lt;/p&gt;

&lt;p&gt;By the end of this post, you will have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Security and Governance Agent with focused security tools&lt;/li&gt;
&lt;li&gt;Security semantic views mapped to natural language&lt;/li&gt;
&lt;li&gt;Orchestrator routing across Admin, Cost Optimizer, and Security agents&lt;/li&gt;
&lt;li&gt;A practical triage workflow for failed logins, privilege risk, and unauthorized access&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Why Add a Security Specialist?
&lt;/h2&gt;

&lt;p&gt;The first two agents are strong for operations and spend, but security requires a different lens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access control and role hygiene&lt;/li&gt;
&lt;li&gt;Failed login patterns and anomaly detection&lt;/li&gt;
&lt;li&gt;Unauthorized access attempts&lt;/li&gt;
&lt;li&gt;Inactive users with active privileges&lt;/li&gt;
&lt;li&gt;Compliance-friendly audit summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Could one large agent do everything? Sometimes. But specialized agents are easier to maintain, safer to evolve, and easier to test.&lt;/p&gt;
&lt;h2&gt;
  
  
  Final Team Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Question (natural language)
        |
  Orchestrator Agent
   /      |        \
Admin   Cost     Security
Agent  Optimizer Governance
                 Agent
        \    |    /
      Unified Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Role of each specialist
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Admin Agent: usage, credits, storage, operational metrics&lt;/li&gt;
&lt;li&gt;Cost Optimizer Agent: idle compute, rightsizing, optimization opportunities&lt;/li&gt;
&lt;li&gt;Security and Governance Agent: roles, privileges, failed logins, unauthorized access, audits&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Security Pattern (Same Foundation as Parts 1 and 2)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Base Views
&lt;/h3&gt;

&lt;p&gt;Create security-focused views over &lt;code&gt;SNOWFLAKE.ACCOUNT_USAGE&lt;/code&gt;, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Role hierarchy and privilege grants&lt;/li&gt;
&lt;li&gt;Failed login attempts and anomaly severity&lt;/li&gt;
&lt;li&gt;Excessive or unused privileged access&lt;/li&gt;
&lt;li&gt;Unauthorized access attempts&lt;/li&gt;
&lt;li&gt;User and role audit summaries&lt;/li&gt;
&lt;li&gt;Network policy activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementation file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sql/08_create_security_governance_views.sql&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 2: Semantic Views
&lt;/h3&gt;

&lt;p&gt;Map these views into natural language dimensions, facts, and metrics so Cortex Analyst can reason over them.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SV_LOGIN_ANOMALIES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SV_EXCESSIVE_PRIVILEGES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SV_UNAUTHORIZED_ACCESS_ATTEMPTS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SV_USER_ROLE_AUDIT&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementation file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sql/09_create_security_governance_semantic_views.sql&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 3: Create the Security Agent
&lt;/h3&gt;

&lt;p&gt;Define a dedicated agent with explicit analyst tools for each security domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;AGENT&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;APP_DB&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;APP_SCHEMA&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SECURITY_AGENT_NAME&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;COMMENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Security and Governance agent for access control and compliance'&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SPECIFICATION&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"You are a Security and Governance assistant..."&lt;/span&gt;
    &lt;span class="n"&gt;orchestration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"Route role hierarchy questions to RoleHierarchyAnalyst; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;
                   privilege grants to PrivilegeGrantAnalyst; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;
                   failed logins to FailedLoginAnalyst; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;
                   login anomalies to LoginAnomalyDetector..."&lt;/span&gt;

  &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tool_spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"cortex_analyst_text_to_sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"RoleHierarchyAnalyst"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tool_spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"cortex_analyst_text_to_sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"PrivilegeGrantAnalyst"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tool_spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"cortex_analyst_text_to_sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"FailedLoginAnalyst"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tool_spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"cortex_analyst_text_to_sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"LoginAnomalyDetector"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tool_spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"cortex_analyst_text_to_sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"ExcessivePrivilegeAnalyst"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tool_spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"cortex_analyst_text_to_sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"UnauthorizedAccessAnalyst"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tool_spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"cortex_analyst_text_to_sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"UserAuditAnalyst"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementation file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sql/11_create_security_governance_agent.sql&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Route Through the Orchestrator
&lt;/h3&gt;

&lt;p&gt;Your orchestrator now includes &lt;code&gt;SecurityAgent&lt;/code&gt; as a first-class route target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- in sql/12_create_orchestrator_agent.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- Questions about security, roles, privileges, failed logins, unauthorized access -&amp;gt; SecurityAgent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can also fan out to multiple agents for blended questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Security-Only Routing
&lt;/h2&gt;

&lt;p&gt;Question:&lt;br&gt;
"Are there suspicious login failures in the last 7 days?"&lt;/p&gt;

&lt;p&gt;What happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Orchestrator classifies this as security monitoring&lt;/li&gt;
&lt;li&gt;Routes to SecurityAgent&lt;/li&gt;
&lt;li&gt;SecurityAgent uses &lt;code&gt;SV_LOGIN_ANOMALIES&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Returns severity-based findings and recommendations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Login Anomaly Summary (Last 7 Days)

Critical: 2 users with &amp;gt;= 10 failed attempts/hour
High: 5 users with 5-9 failed attempts/hour
Pattern: Multiple failed attempts from distinct IPs for USER_X

Recommendation:
- Lock and verify impacted accounts
- Enforce MFA re-registration for affected users
- Review network policy and source IP ranges
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example 2: Privilege Governance
&lt;/h2&gt;

&lt;p&gt;Question:&lt;br&gt;
"Which users have ACCOUNTADMIN or SECURITYADMIN but low recent usage?"&lt;/p&gt;

&lt;p&gt;What happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Routed to SecurityAgent&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;SV_EXCESSIVE_PRIVILEGES&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Identifies high-risk assignments with low usage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Excessive Privilege Findings

Users flagged: 4
Critical: 2 users with no privileged role usage in 60+ days
High: 2 users with &amp;lt; 5 privileged queries in 90 days

Recommendation:
- Revoke unused privileged grants
- Replace standing privilege with just-in-time elevation
- Document business justification for remaining elevated users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example 3: Cross-Agent Investigation
&lt;/h2&gt;

&lt;p&gt;Question:&lt;br&gt;
"Why are costs up and is there any security risk around this?"&lt;/p&gt;

&lt;p&gt;What happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Orchestrator identifies multi-domain intent&lt;/li&gt;
&lt;li&gt;Routes to AdminAgent + CostOptimizerAgent + SecurityAgent&lt;/li&gt;
&lt;li&gt;Aggregates usage, optimization, and security posture into one answer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Integrated Account Assessment

Operations (Admin Agent)
- Compute credits up 18% month-over-month
- Growth concentrated in two ETL warehouses

Optimization (Cost Optimizer Agent)
- Idle percentage &amp;gt; 60% on one ETL warehouse
- Suggested AUTO_SUSPEND change could reduce waste materially

Security (Security Agent)
- One privileged user inactive but still assigned elevated role
- Increased failed login attempts from multiple IPs for two accounts

Priority Actions
1) Apply warehouse auto-suspend tuning
2) Review elevated role assignments and revoke unused grants
3) Investigate failed-login anomalies and tighten network controls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment Sequence
&lt;/h2&gt;

&lt;p&gt;If you already deployed Parts 1 and 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) Security views&lt;/span&gt;
sql/08_create_security_governance_views.sql

&lt;span class="c"&gt;# 2) Security semantic layer&lt;/span&gt;
sql/09_create_security_governance_semantic_views.sql

&lt;span class="c"&gt;# 3) Security agent&lt;/span&gt;
sql/11_create_security_governance_agent.sql

&lt;span class="c"&gt;# 4) Orchestrator (includes SecurityAgent routing)&lt;/span&gt;
sql/12_create_orchestrator_agent.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Queries
&lt;/h2&gt;

&lt;p&gt;Direct test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CORTEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DATA_AGENT_RUN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'&amp;lt;APP_DB&amp;gt;.&amp;lt;APP_SCHEMA&amp;gt;.&amp;lt;SECURITY_AGENT_NAME&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'{"messages":[{"role":"user","content":[{"type":"text","text":"Show failed login anomalies by severity"}]}]}'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Orchestrated test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CORTEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DATA_AGENT_RUN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'&amp;lt;APP_DB&amp;gt;.&amp;lt;APP_SCHEMA&amp;gt;.&amp;lt;ORCHESTRATOR_AGENT_NAME&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'{"messages":[{"role":"user","content":[{"type":"text","text":"Analyze account risk: costs, idle warehouses, and failed logins"}]}]}'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SNOWFLAKE.ACCOUNT_USAGE&lt;/code&gt; views have latency. For near-real-time incident response, combine this with event-driven telemetry.&lt;/li&gt;
&lt;li&gt;Keep role and privilege reviews on a recurring schedule (monthly or quarterly depending on policy).&lt;/li&gt;
&lt;li&gt;Use placeholders and environment-specific grants consistently:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;APP_DB&amp;gt;.&amp;lt;APP_SCHEMA&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;EXEC_WAREHOUSE&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;ADMIN_ROLE&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;DEVELOPER_ROLE&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What We Have Now
&lt;/h2&gt;

&lt;p&gt;After Part 3, you have a complete multi-agent Snowflake administration team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Admin Agent for operational visibility&lt;/li&gt;
&lt;li&gt;Cost Optimizer Agent for efficiency and savings&lt;/li&gt;
&lt;li&gt;Security and Governance Agent for risk and compliance&lt;/li&gt;
&lt;li&gt;Orchestrator Agent for seamless, natural-language routing across all three&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where multi-agent design pays off: each specialist stays focused, and users still ask one simple question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Security base views: &lt;code&gt;sql/08_create_security_governance_views.sql&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Security semantic views: &lt;code&gt;sql/09_create_security_governance_semantic_views.sql&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Security agent: &lt;code&gt;sql/11_create_security_governance_agent.sql&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Orchestrator routing: &lt;code&gt;sql/12_create_orchestrator_agent.sql&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Security skill guidance: &lt;code&gt;skills/security-governance/SKILL.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;

&lt;p&gt;Complete implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin/tree/main/sql" rel="noopener noreferrer"&gt;SQL Folder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LALITHASWAROOPK/agent_snowflake_admin/blob/main/mcp/server.py" rel="noopener noreferrer"&gt;MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Part 1 gave us visibility.&lt;br&gt;
Part 2 gave us optimization.&lt;br&gt;
Part 3 gives us governance and risk control.&lt;/p&gt;

&lt;p&gt;Same architecture pattern, broader coverage:&lt;br&gt;
&lt;strong&gt;Views -&amp;gt; Semantic Views -&amp;gt; Specialist Agent -&amp;gt; Orchestrator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add automated alerting and ticket creation workflows&lt;/li&gt;
&lt;li&gt;Add remediation playbooks per risk severity&lt;/li&gt;
&lt;li&gt;Add environment-level policy checks for continuous compliance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions or feedback? Drop a comment below.&lt;/p&gt;

&lt;p&gt;Part 1: &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/ask-your-snowflake-account-anything-build-an-ai-admin-agent-with-cortex-github-copilot-1mk6"&gt;Ask Your Snowflake Account Anything - Build an AI Admin Agent&lt;/a&gt;&lt;br&gt;
Part 2: &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/from-one-agent-to-many-building-a-multi-agent-team-for-snowflake-administration-part-2-379d"&gt;From One Agent to Many - Building a Multi-Agent Team&lt;/a&gt;&lt;br&gt;
Part 3: Security and Governance Agent (this post)&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>ai</category>
      <category>security</category>
      <category>governance</category>
    </item>
    <item>
      <title>Mirroring Snowflake Iceberg into Microsoft Fabric: The Gotchas - Part 1:</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 11 Jun 2026 22:53:42 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/what-i-learned-running-snowflake-iceberg-mirroring-in-microsoft-fabric-and-debugging-real-1151</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/what-i-learned-running-snowflake-iceberg-mirroring-in-microsoft-fabric-and-debugging-real-1151</guid>
      <description>&lt;p&gt;&lt;strong&gt;Previously:&lt;/strong&gt; In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/setting-up-snowflake-power-bi-connectivity-with-azure-ad-sso-and-auto-provisioning-1gda"&gt;Power BI Connectivity with Azure AD&lt;/a&gt;, I walked through enabling end-to-end SSO from Power BI to Snowflake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this post:&lt;/strong&gt; A hands-on implementation log for Snowflake mirroring (including Iceberg tables) in Microsoft Fabric — what official docs confirm, what broke in production, and the runbook that now works. If you're seeing differences between Mirroring Status and SQL Analytics Endpoint, this should help.&lt;/p&gt;

&lt;p&gt;Our goal was to serve analysts from Fabric's shared OneLake layer instead of having every downstream workflow repeatedly query Snowflake directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fabric Mirroring for Snowflake continuously replicates source data into OneLake.&lt;/li&gt;
&lt;li&gt;For Iceberg tables, Fabric replicates metadata to OneLake via shortcuts to the underlying storage and OneLake exposes data in Delta format for Fabric workloads.&lt;/li&gt;
&lt;li&gt;SQL Analytics Endpoint is read-only and can show type/metadata limitations even when mirroring is healthy.&lt;/li&gt;
&lt;li&gt;New or changed tables can take time to appear consistently in SQL Analytics Endpoint because replication and SQL metadata sync are separate steps.&lt;/li&gt;
&lt;li&gt;Connection configuration accuracy matters (especially warehouse identity and permissions).&lt;/li&gt;
&lt;li&gt;Not all Snowflake table types are eligible for mirroring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1o73nrm7iqo1g8vlpsn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1o73nrm7iqo1g8vlpsn.png" alt=" " width="800" height="154"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I validated against official Microsoft docs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. How mirroring works for Snowflake in Fabric
&lt;/h3&gt;

&lt;p&gt;From Microsoft documentation, mirroring creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A mirrored database item in Fabric&lt;/li&gt;
&lt;li&gt;A SQL Analytics Endpoint for querying the mirrored data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Snowflake sources specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Iceberg table metadata is replicated into OneLake using shortcuts to the storage that contains those Iceberg tables.&lt;/li&gt;
&lt;li&gt;OneLake automatically converts those Iceberg tables to Delta Lake formatted tables for Fabric workloads.&lt;/li&gt;
&lt;li&gt;Managed table data is replicated into OneLake and converted to analytics-ready format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means consumption in Fabric is against replicated data in OneLake, not a live pass-through to Snowflake. When users say "SQL endpoint looks wrong," I now split diagnosis into replication-plane checks vs SQL metadata-plane checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. SQL Analytics Endpoint is read-only and has data type/materialization limits
&lt;/h3&gt;

&lt;p&gt;Microsoft documents that SQL Analytics Endpoint uses the same engine family and constraints as Fabric Warehouse for persisted/object materialized types. In practice this explains why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mirroring can be healthy&lt;/li&gt;
&lt;li&gt;But some columns can be missing/truncated/not surfaced as expected in SQL Analytics Endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially important when source types do not map cleanly to endpoint-supported persisted types.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Snowflake mirroring limitations that matter in design
&lt;/h3&gt;

&lt;p&gt;Official Snowflake mirroring limitations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regular/native table replication focus (not all table categories are supported)&lt;/li&gt;
&lt;li&gt;A current mirrored table-count limit per mirrored database&lt;/li&gt;
&lt;li&gt;Schema-change replication nuances (some schema changes need data changes to trigger propagation)&lt;/li&gt;
&lt;li&gt;Backoff behavior when source tables are idle&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Cost and serving model
&lt;/h3&gt;

&lt;p&gt;From Microsoft guidance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fabric query workloads (SQL, Spark, BI) consume Fabric capacity.&lt;/li&gt;
&lt;li&gt;Snowflake compute can be incurred for data-change reads during replication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a one-time load model; mirroring is continuous synchronization. Total cost depends on change volume, replication activity, and Fabric-side query demand.&lt;/p&gt;

&lt;p&gt;Why we still chose it: it reduced repeated direct-query pressure from many downstream users and gave us a shared serving layer across SQL, Spark, and BI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Incident Log: What broke and what fixed it
&lt;/h2&gt;

&lt;p&gt;Simple-looking errors often had multiple causes: connection identity, permissions, metadata timing, and endpoint type handling. The key discipline was separating what docs explicitly confirm from support heuristics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ticket #1–2: Connection and warehouse errors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;Mirroring refresh failed with warehouse-not-found / not-authorized style errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we found
&lt;/h3&gt;

&lt;p&gt;The configured warehouse identity in Fabric connection did not align with the warehouse defined/accessible in Snowflake for the mirroring principal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Stop replication&lt;/li&gt;
&lt;li&gt;Recreate the Snowflake connection in Fabric&lt;/li&gt;
&lt;li&gt;Re-enter server/warehouse carefully&lt;/li&gt;
&lt;li&gt;Ensure the principal has warehouse usage + required mirroring privileges&lt;/li&gt;
&lt;li&gt;Rebind mirrored database to the corrected connection&lt;/li&gt;
&lt;li&gt;Restart replication and monitor&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Important precision
&lt;/h3&gt;

&lt;p&gt;Microsoft docs explicitly tell you to normalize server entry format (for example remove protocol and use lowercase server host format in setup guidance). For warehouse value handling, my operational practice is to treat it as an exact identifier and validate both existence and authorization before restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fau0lox9pr0wd17vt6s83.png" alt=" " width="340" height="646"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Ticket #3: Mirroring successful, but SQL Analytics Endpoint shows column issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;Mirroring status looked healthy, but querying via SQL Analytics Endpoint showed warnings or incomplete column exposure for some tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause pattern
&lt;/h3&gt;

&lt;p&gt;Endpoint-level SQL metadata and type mapping did not always represent source columns as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this happens
&lt;/h3&gt;

&lt;p&gt;This behavior is aligned with documented SQL Analytics Endpoint limitations and data type mapping constraints. Mirroring into OneLake can still be successful while SQL endpoint representation is partially constrained.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo2731uu15u8886jw60st.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo2731uu15u8886jw60st.png" alt=" " width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mitigations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Validate data in OneLake directly (for example via Spark) when endpoint output looks suspicious&lt;/li&gt;
&lt;li&gt;Cast unsupported/problematic source columns to endpoint-friendly types in curated tables/views&lt;/li&gt;
&lt;li&gt;Use SQL endpoint refresh and metadata sync checks where relevant&lt;/li&gt;
&lt;li&gt;For critical workloads, create curated serving layers specifically for endpoint compatibility&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architecture model that helped me
&lt;/h2&gt;

&lt;p&gt;Think about Snowflake mirroring in Fabric as three planes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replication plane&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Movement/sync of data and metadata into OneLake&lt;/li&gt;
&lt;li&gt;Includes Iceberg metadata handling + managed table replication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Storage/open format plane&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data persisted in OneLake with Delta interoperability for Fabric engines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Serving plane&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Analytics Endpoint as read-only, T-SQL-friendly surface&lt;/li&gt;
&lt;li&gt;Useful, but not equivalent to full source-type fidelity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most confusion in operations comes from mixing plane #1 health with plane #3 usability.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdcpccb3je8m3za4npqb.png" alt=" " width="322" height="706"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  My production runbook now
&lt;/h2&gt;

&lt;p&gt;Before enabling mirroring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate Snowflake privileges for mirroring principal (&lt;code&gt;CREATE STREAM&lt;/code&gt;, &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;SHOW TABLES&lt;/code&gt;, &lt;code&gt;DESCRIBE TABLES&lt;/code&gt; per Microsoft/Snowflake guidance)&lt;/li&gt;
&lt;li&gt;Validate server entry format and connection parameters&lt;/li&gt;
&lt;li&gt;Validate warehouse existence and effective permissions for principal&lt;/li&gt;
&lt;li&gt;Identify which tables are regular/native and eligible&lt;/li&gt;
&lt;li&gt;For Iceberg, confirm object store (e.g. S3) permissions up front: the mirroring principal needs read access to the underlying bucket where Iceberg data files are stored, not just Snowflake-level privileges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After enabling mirroring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor table-level replication status and last refresh&lt;/li&gt;
&lt;li&gt;Distinguish OneLake landing success from SQL endpoint metadata availability&lt;/li&gt;
&lt;li&gt;Add smoke tests in both Spark and SQL endpoint&lt;/li&gt;
&lt;li&gt;Track Snowflake compute impact during replication windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When troubleshooting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If connection/auth errors: recreate connection cleanly, verify identifiers and grants&lt;/li&gt;
&lt;li&gt;If missing columns in endpoint: check SQL endpoint limitations and data type mappings, then curate/cast&lt;/li&gt;
&lt;li&gt;If schema changes not reflected: trigger/verify data change propagation and refresh metadata&lt;/li&gt;
&lt;li&gt;If large-table updates are massive: consider stop/start strategy per documented performance guidance&lt;/li&gt;
&lt;li&gt;If newly added tables don't appear in SQL endpoint: wait for initial replication to complete, validate data landed in OneLake/Spark first, then trigger endpoint metadata refresh — endpoint availability is near-real-time, not instant&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Common misconceptions I had (now corrected)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Misconception 1: "Mirroring success means SQL endpoint is guaranteed complete"
&lt;/h3&gt;

&lt;p&gt;Not always true. Replication and endpoint representation are related but not identical concerns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Misconception 2: "All Snowflake table variants mirror the same way"
&lt;/h3&gt;

&lt;p&gt;No. Supported categories are explicitly constrained; design with documented limitations first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Misconception 3: "If it says queried live, Fabric is directly querying Snowflake"
&lt;/h3&gt;

&lt;p&gt;Operationally, Fabric mirroring is a replication model into OneLake with Fabric-side query consumption.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fabric mirroring for Snowflake Iceberg works well when you separate replication health from SQL endpoint behavior.&lt;/li&gt;
&lt;li&gt;SQL Analytics Endpoint issues do not always mean replication failure.&lt;/li&gt;
&lt;li&gt;Connection hygiene (especially warehouse + auth correctness) prevents a surprising number of incidents.&lt;/li&gt;
&lt;li&gt;For production, design a compatibility layer for endpoint consumption instead of relying on raw source type fidelity.&lt;/li&gt;
&lt;li&gt;This was genuinely painful to implement end-to-end, but the pain forced a better operating model and a sharper troubleshooting discipline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of those integrations where knowing the documented constraints upfront saves far more time than reactive firefighting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Neutrality and scope disclosure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This post reflects my hands-on implementation experience and support-ticket learnings.&lt;/li&gt;
&lt;li&gt;It is not sponsored content and not intended as a blanket endorsement of any product.&lt;/li&gt;
&lt;li&gt;Platform behavior, latency, and cost outcomes can vary by workload shape, table volume, schema patterns, and operating model.&lt;/li&gt;
&lt;li&gt;The intent is practical: help others avoid common mistakes by combining official documentation with real incident patterns.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Sources (official)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Mirroring Snowflake in Microsoft Fabric:
&lt;a href="https://learn.microsoft.com/en-us/fabric/mirroring/snowflake" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/fabric/mirroring/snowflake&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tutorial: Configure mirrored Snowflake:
&lt;a href="https://learn.microsoft.com/en-us/fabric/mirroring/snowflake-tutorial" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/fabric/mirroring/snowflake-tutorial&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Snowflake mirroring limitations:
&lt;a href="https://learn.microsoft.com/en-us/fabric/mirroring/snowflake-limitations" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/fabric/mirroring/snowflake-limitations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Troubleshoot mirrored databases:
&lt;a href="https://learn.microsoft.com/en-us/fabric/mirroring/troubleshooting" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/fabric/mirroring/troubleshooting&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Snowflake mirroring FAQ:
&lt;a href="https://learn.microsoft.com/en-us/fabric/mirroring/snowflake-mirroring-faq" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/fabric/mirroring/snowflake-mirroring-faq&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SQL Analytics Endpoint limitations:
&lt;a href="https://learn.microsoft.com/en-us/fabric/data-engineering/lakehouse-sql-analytics-endpoint#limitations" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/fabric/data-engineering/lakehouse-sql-analytics-endpoint#limitations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Data types in Fabric Data Warehouse / SQL endpoint mapping:
&lt;a href="https://learn.microsoft.com/en-us/fabric/data-warehouse/data-types" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/fabric/data-warehouse/data-types&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>microsoft</category>
      <category>dbt</category>
      <category>snowflake</category>
      <category>iceberg</category>
    </item>
    <item>
      <title>Evaluating Adaptive Warehouses for ETL: Why We Reverted to Standard</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Wed, 27 May 2026 14:13:16 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/evaluating-adaptive-warehouses-for-etl-why-we-reverted-to-standard-239f</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/evaluating-adaptive-warehouses-for-etl-why-we-reverted-to-standard-239f</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/how-a-simple-warehouse-resize-saved-us-11-in-daily-credits-while-boosting-performance-2753"&gt;our last post&lt;/a&gt; we showed how upsizing a Bronze ETL warehouse from XSmall to Small reduced daily credits by 11%. This post examines a different question: does Snowflake's Adaptive warehouse model (preview) improve on what a well-tuned Standard warehouse already delivers?&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://docs.snowflake.com/en/user-guide/warehouses-adaptive" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;docs.snowflake.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;For our Silver layer workload, the answer was no.&lt;/p&gt;




&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;WH_ETL_SILVER_01&lt;/code&gt; is our dedicated warehouse for Silver layer data loads — long-running transformation queries at a steady, predictable rate of ~2,000 queries/day. On May 12, 2026 we migrated it from Standard to Adaptive to evaluate the new model under production conditions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Standard Config&lt;/th&gt;
&lt;th&gt;Adaptive Config&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;STANDARD&lt;/td&gt;
&lt;td&gt;ADAPTIVE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;Max: Small&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency/Burst&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MAX_CLUSTER_COUNT = 3&lt;/td&gt;
&lt;td&gt;QUERY_THROUGHPUT_MULTIPLIER = 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto-Suspend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;60 seconds&lt;/td&gt;
&lt;td&gt;Managed by Snowflake&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After 16 days of production data, we reverted.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Queuing That Wouldn't Stop Growing
&lt;/h2&gt;

&lt;p&gt;Within the first week on adaptive, we started seeing queries queue. By week three, the queue rate had grown to 3.4% — and still climbing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Period&lt;/th&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;Queue Rate&lt;/th&gt;
&lt;th&gt;Avg Wait (queued)&lt;/th&gt;
&lt;th&gt;Max Wait&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apr 1 – May 11&lt;/td&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;td&gt;~0%&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 12–15&lt;/td&gt;
&lt;td&gt;Adaptive Week 1&lt;/td&gt;
&lt;td&gt;0.1%&lt;/td&gt;
&lt;td&gt;~10s&lt;/td&gt;
&lt;td&gt;102s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 16–22&lt;/td&gt;
&lt;td&gt;Adaptive Week 2&lt;/td&gt;
&lt;td&gt;2.5%&lt;/td&gt;
&lt;td&gt;80–98s&lt;/td&gt;
&lt;td&gt;490s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 23–27&lt;/td&gt;
&lt;td&gt;Adaptive Week 3&lt;/td&gt;
&lt;td&gt;3.4%&lt;/td&gt;
&lt;td&gt;65–80s&lt;/td&gt;
&lt;td&gt;508s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Under Standard with the same ~2,000 queries/day load, queueing was effectively zero. Two queries queued on the worst day, for 100ms each.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fair Comparison: Matched Load Windows
&lt;/h2&gt;

&lt;p&gt;To avoid skewing results by different query volumes, we compared periods with equivalent daily throughput.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard baseline&lt;/strong&gt;: May 1–11 (11 days, avg 2,151 queries/day)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive comparison&lt;/strong&gt;: May 12–14, 18–22, 25–26 (10 days, avg 2,060 queries/day)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Query Performance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Standard&lt;/th&gt;
&lt;th&gt;Adaptive&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avg Elapsed Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19.9s&lt;/td&gt;
&lt;td&gt;22.8s&lt;/td&gt;
&lt;td&gt;+15% slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Median Elapsed Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;256ms&lt;/td&gt;
&lt;td&gt;267ms&lt;/td&gt;
&lt;td&gt;+4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P95 Elapsed Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.1s&lt;/td&gt;
&lt;td&gt;20.4s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+45% slower&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avg Execution Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19.6s&lt;/td&gt;
&lt;td&gt;21.0s&lt;/td&gt;
&lt;td&gt;+7% slower&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Standard&lt;/th&gt;
&lt;th&gt;Adaptive&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avg Daily Credits&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.2&lt;/td&gt;
&lt;td&gt;19.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+26% more expensive&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Credits Per Query&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.0071&lt;/td&gt;
&lt;td&gt;0.0093&lt;/td&gt;
&lt;td&gt;+31% more expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Queuing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Standard&lt;/th&gt;
&lt;th&gt;Adaptive&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Queue Rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.3%&lt;/td&gt;
&lt;td&gt;2.2% (growing)&lt;/td&gt;
&lt;td&gt;+7x worse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avg Queue Wait&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3.3ms&lt;/td&gt;
&lt;td&gt;1,543ms&lt;/td&gt;
&lt;td&gt;+46,000% worse&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How We Measured
&lt;/h2&gt;

&lt;p&gt;We used the same &lt;code&gt;ACCOUNT_USAGE&lt;/code&gt; views from the previous post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query Performance (Matched Windows)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-11'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'Standard'&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'Adaptive'&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_elapsed_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_elapsed_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MEDIAN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_elapsed_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;median_elapsed_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PERCENTILE_CONT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;WITHIN&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_elapsed_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p95_elapsed_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queued_overload_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_queued_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COUNT_IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queued_overload_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                        &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;queue_rate_pct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_USAGE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QUERY_HISTORY&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;warehouse_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WH_ETL_SILVER_01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-01'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Credit Consumption
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-11'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'Standard'&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'Adaptive'&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credits_used_compute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_daily_credits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credits_used_compute&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                      &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_credits&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_USAGE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WAREHOUSE_METERING_HISTORY&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;warehouse_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WH_ETL_SILVER_01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-01'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Adaptive Doesn't Fit This Workload
&lt;/h2&gt;

&lt;p&gt;Adaptive warehouses are designed for &lt;strong&gt;variable, unpredictable workloads&lt;/strong&gt; — dashboards, ad-hoc analytics, mixed query sizes. Our Silver pipeline is the opposite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Steady ~2,000 queries/day&lt;/li&gt;
&lt;li&gt;Predictable concurrency (same pipeline, same schedule)&lt;/li&gt;
&lt;li&gt;Long-running transformation queries — not short, bursty ones&lt;/li&gt;
&lt;li&gt;Consistent data volumes day-to-day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;QUERY_THROUGHPUT_MULTIPLIER = 3&lt;/code&gt; cap was insufficient for peak concurrency windows. The adaptive model's dynamic scaling overhead — which adds value when workloads are unpredictable — just introduced latency without benefit here. The Standard config's fixed multi-cluster model handled this workload profile cleanly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recommendation: Revert to Standard
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_SILVER_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_TYPE&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SMALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SCALING_POLICY&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected impact after reverting:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eliminate queuing (proven 0% queue rate at this load level)&lt;/li&gt;
&lt;li&gt;Save ~26% on daily credits&lt;/li&gt;
&lt;li&gt;Reduce P95 latency by 45%&lt;/li&gt;
&lt;li&gt;Restore predictable, consistent performance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  If You Must Stay on Adaptive
&lt;/h3&gt;

&lt;p&gt;Snowflake's official guidance is clear: &lt;em&gt;increase &lt;code&gt;QUERY_THROUGHPUT_MULTIPLIER&lt;/code&gt; to reduce queuing&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_SILVER_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;QUERY_THROUGHPUT_MULTIPLIER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would likely resolve the queuing issue. However, for a &lt;strong&gt;predictable, steady-state ETL workload&lt;/strong&gt;, this approach adds operational complexity without strategic benefit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're now tuning a multiplier instead of configuring a fixed cluster count&lt;/li&gt;
&lt;li&gt;Higher throughput capacity may increase peak concurrent spend, requiring new cost monitoring&lt;/li&gt;
&lt;li&gt;For a pipeline that runs the same way every day, the adaptive model's flexibility adds overhead without value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; Adaptive's tuning knobs are powerful for variable workloads. For predictable ETL, Standard's simplicity wins operationally — and financially.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive is not universally better&lt;/strong&gt; — it adds overhead that only pays off for variable, unpredictable workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always compare at matched load&lt;/strong&gt; — raw averages across different query volumes are misleading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queuing is the canary&lt;/strong&gt; — a worsening queue rate on adaptive signals a workload mismatch, not just an under-tuned multiplier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard multi-cluster remains the better fit&lt;/strong&gt; for batch/ETL pipelines with consistent concurrency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reserve adaptive for variable workloads&lt;/strong&gt; — ad-hoc analytics or environments where query volume swings 5–10x daily.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;The lesson from &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/how-a-simple-warehouse-resize-saved-us-11-in-daily-credits-while-boosting-performance-2753"&gt;post &lt;/a&gt;was: in some workloads, bigger can be cheaper. The lesson here is the inverse: newer is not always better. The right warehouse type depends on workload shape — steady pipelines and dynamic workloads have fundamentally different resource patterns, and Snowflake's warehouse models reflect that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure and Scope
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Results reflect our specific environment and workload profile; outcomes may vary.&lt;/li&gt;
&lt;li&gt;Analysis period: May 1–27, 2026 | Warehouse: &lt;code&gt;WH_ETL_SILVER_01&lt;/code&gt; | Workload: Silver layer production loads&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This analysis used Snowflake built-in &lt;code&gt;ACCOUNT_USAGE&lt;/code&gt; views only. No third-party monitoring stack required.&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>adaptive</category>
      <category>warehouse</category>
      <category>workload</category>
    </item>
    <item>
      <title>How a Simple Warehouse Resize Saved Us 11% in Daily Credits While Boosting Performance</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 07 May 2026 06:56:45 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/how-a-simple-warehouse-resize-saved-us-11-in-daily-credits-while-boosting-performance-2753</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/how-a-simple-warehouse-resize-saved-us-11-in-daily-credits-while-boosting-performance-2753</guid>
      <description>&lt;p&gt;Our team hit a familiar Snowflake paradox: slower ETL runs arrived at the same time as FinOps alerts about rising credits.&lt;/p&gt;

&lt;p&gt;The warehouse in question was &lt;code&gt;WH_ETL_BRONZE_01&lt;/code&gt;, a multi-cluster warehouse dedicated to Bronze layer ingestion and merge workloads. What looked like a simple cost problem turned out to be a workload-isolation and concurrency problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;We started with this setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_BRONZE_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MIN_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SCALING_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CONCURRENCY_LEVEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- default behavior&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we saw both of these at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher queue times (&lt;code&gt;queued_overload_time&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Higher daily credits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, we were loading &lt;strong&gt;1,500+ tables&lt;/strong&gt; through this single warehouse. That meant heavyweight Bronze MERGE workloads and smaller ingestion/utility queries were all competing in the same execution pool.&lt;/p&gt;

&lt;p&gt;The critical issue was workload mix. Long-running MERGE statements (30 to 60 minutes) were running alongside tiny queries. When too many heavy MERGE statements landed on the same node/cluster, they competed for resources and all slowed down.&lt;/p&gt;

&lt;p&gt;In other words, even with multi-cluster enabled, assignment patterns could create unstable performance if too many heavyweight queries were packed together.&lt;/p&gt;

&lt;p&gt;We also learned we needed &lt;strong&gt;workload separation&lt;/strong&gt;: large-table loads should run in a dedicated warehouse so they do not contend with smaller table loads and operational queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Changed
&lt;/h2&gt;

&lt;p&gt;This was not a single before/after discovery from history. We ran controlled experiments in sequence, while splitting the largest table loads into a separate warehouse path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Baseline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_BRONZE_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MIN_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SCALING_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CONCURRENCY_LEVEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Phase 2: First Experiment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_BRONZE_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MIN_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SCALING_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CONCURRENCY_LEVEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Goal: force scale-out behavior earlier and reduce the chance that many heavy MERGE jobs share the same node.&lt;/p&gt;

&lt;p&gt;At the same time, we moved larger-table processing to a separate warehouse so those jobs would not compete with the remaining 1,500+ table ingestion flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Final Optimization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;WH_ETL_BRONZE_01&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SMALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MIN_CLUSTER_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SCALING_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STANDARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_SUSPEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_CONCURRENCY_LEVEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This became the best balance for our workload profile.&lt;/p&gt;

&lt;p&gt;Final state for this ETL warehouse path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SMALL&lt;/code&gt; size&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAX_CONCURRENCY_LEVEL = 3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Large-table workloads isolated to a separate warehouse&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Helped
&lt;/h2&gt;

&lt;p&gt;From deeper analysis of the workload behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The number of queries and output volume could look similar across runs, but bytes scanned at table level could still rise.&lt;/li&gt;
&lt;li&gt;Heavy MERGE overlap was a key instability driver.&lt;/li&gt;
&lt;li&gt;On slow runs, many heavy MERGE statements could get assigned to the same node, causing resource contention.&lt;/li&gt;
&lt;li&gt;On fast runs, fewer heavy MERGE statements shared a node, leaving room for short-running queries.&lt;/li&gt;
&lt;li&gt;Isolating large-table workloads reduced cross-workload contention in the primary Bronze ETL warehouse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lowering concurrency reduced the tendency to over-pack heavyweight merges. Then moving to &lt;code&gt;SMALL&lt;/code&gt; provided enough per-query resources to cut elapsed time and queueing further. Separating large-table loads into a dedicated warehouse stabilized performance for the rest of the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Max Clusters&lt;/th&gt;
&lt;th&gt;Max Concurrency&lt;/th&gt;
&lt;th&gt;Intent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;XSMALL&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Initial setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Experiment&lt;/td&gt;
&lt;td&gt;XSMALL&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Force earlier scale-out / reduce heavy-query packing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final&lt;/td&gt;
&lt;td&gt;SMALL&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Balance throughput, queueing, and cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two final intentional changes vs baseline were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WAREHOUSE_SIZE&lt;/code&gt;: &lt;code&gt;XSMALL&lt;/code&gt; -&amp;gt; &lt;code&gt;SMALL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MAX_CONCURRENCY_LEVEL&lt;/code&gt;: &lt;code&gt;8&lt;/code&gt; -&amp;gt; &lt;code&gt;3&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How We Measured
&lt;/h2&gt;

&lt;p&gt;We used Snowflake &lt;code&gt;ACCOUNT_USAGE&lt;/code&gt; views for both performance and cost, comparing baseline and final optimization windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query Performance (Before/After)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-05'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL Period'&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'SMALL Period'&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_elapsed_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_elapsed_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queued_overload_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_queued_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_elapsed_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;max_elapsed_sec&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_USAGE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QUERY_HISTORY&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;warehouse_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WH_ETL_BRONZE_01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-04-30'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Credit Consumption (Daily)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-05'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'XSMALL Period'&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'SMALL Period'&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credits_used_compute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;daily_credits&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_USAGE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WAREHOUSE_METERING_HISTORY&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;warehouse_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WH_ETL_BRONZE_01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-04-30'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Period&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Daily Credits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline (5 days)&lt;/td&gt;
&lt;td&gt;XSMALL&lt;/td&gt;
&lt;td&gt;15.12/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimized (3 days)&lt;/td&gt;
&lt;td&gt;SMALL&lt;/td&gt;
&lt;td&gt;13.50/day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Daily credits dropped by about &lt;strong&gt;11%&lt;/strong&gt; after upsizing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;XSMALL&lt;/th&gt;
&lt;th&gt;SMALL&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avg query time&lt;/td&gt;
&lt;td&gt;26.15s&lt;/td&gt;
&lt;td&gt;24.13s&lt;/td&gt;
&lt;td&gt;8% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max query time&lt;/td&gt;
&lt;td&gt;3,475s&lt;/td&gt;
&lt;td&gt;2,850s&lt;/td&gt;
&lt;td&gt;18% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total queue time&lt;/td&gt;
&lt;td&gt;2.20 min&lt;/td&gt;
&lt;td&gt;0.28 min&lt;/td&gt;
&lt;td&gt;87% less queuing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note: The intermediate &lt;code&gt;XSMALL&lt;/code&gt; plus concurrency &lt;code&gt;2&lt;/code&gt; phase was used to validate behavior and direction. The published KPI table above compares the stable baseline period against the final tuned period.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Counterintuitive Lesson
&lt;/h2&gt;

&lt;p&gt;In some workloads, a bigger warehouse can cost less.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Faster execution means earlier suspend
&lt;/h3&gt;

&lt;p&gt;Queries completed faster on &lt;code&gt;SMALL&lt;/code&gt;, so the warehouse reached &lt;code&gt;AUTO_SUSPEND = 60&lt;/code&gt; sooner. Less runtime plus less idle overhead translated to fewer daily credits.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Higher concurrency can reduce cluster sprawl
&lt;/h3&gt;

&lt;p&gt;Concurrency must match workload shape. In our case, moving from default &lt;code&gt;8&lt;/code&gt; down to &lt;code&gt;2&lt;/code&gt; first improved isolation for heavy MERGE jobs, then landing at &lt;code&gt;3&lt;/code&gt; with &lt;code&gt;SMALL&lt;/code&gt; gave the right balance of throughput and stability.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Less queueing improves throughput and utilization
&lt;/h3&gt;

&lt;p&gt;With 87% less queue time, work finished in tighter windows. The warehouse did useful work and went to sleep sooner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Do not assume smaller is always cheaper.&lt;/li&gt;
&lt;li&gt;Monitor &lt;code&gt;queued_overload_time&lt;/code&gt; closely. Persistent queueing often means you are under-sized or under-concurrented.&lt;/li&gt;
&lt;li&gt;Tune &lt;code&gt;MAX_CONCURRENCY_LEVEL&lt;/code&gt; with warehouse size and workload type; they should be optimized together.&lt;/li&gt;
&lt;li&gt;Isolate heavyweight large-table workflows into a dedicated warehouse to reduce overlap and contention.&lt;/li&gt;
&lt;li&gt;Use both &lt;code&gt;QUERY_HISTORY&lt;/code&gt; (performance) and &lt;code&gt;WAREHOUSE_METERING_HISTORY&lt;/code&gt; (cost) for before/after decisions.&lt;/li&gt;
&lt;li&gt;Keep aggressive auto-suspend for bursty workloads. Faster queries plus short suspend windows compound savings.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;Warehouse right-sizing is a performance and FinOps exercise, not just a cost-control exercise. In many real workloads, a slightly larger warehouse with slightly higher concurrency can win on both speed and spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure and Scope
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Results reflect our specific environment and workload profile; outcomes may vary.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>snowflake</category>
      <category>dataengineering</category>
      <category>finops</category>
      <category>optimization</category>
    </item>
    <item>
      <title>From Glue to Horizon: Our Real Journey Building an Iceberg Lakehouse on Snowflake</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Mon, 04 May 2026 14:18:45 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/iceberg-lakehouse-on-snowflake-journey-3ln4</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/iceberg-lakehouse-on-snowflake-journey-3ln4</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update — September 2026:&lt;/strong&gt; Snowflake has addressed key concerns from our original Dynamic Tables evaluation. Dynamic Iceberg table cloning now supports database/schema clones, and Dynamic Tables Insights provides actionable recommendations for diagnosing refresh performance. These improvements warrant revisiting our earlier assessment. The article below documents our original experience.&lt;br&gt;
Details: &lt;a href="https://docs.snowflake.com/en/release-notes/2026/other/2026-07-13-dynamic-iceberg-table-clone" rel="noopener noreferrer"&gt;Cloning support&lt;/a&gt; · &lt;a href="https://docs.snowflake.com/en/user-guide/dynamic-tables/insights" rel="noopener noreferrer"&gt;Dynamic Tables Insights&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We set out to build an open lakehouse: Iceberg tables on AWS S3, Spark/Glue for pipelines, Snowflake for analytics compute power. What could go wrong? Everything—from query performance implosions to uncloneable dynamic tables. Here's the unfiltered journey, including why we pivoted to Snowflake-managed Iceberg via Horizon Catalog and abandoned automated dynamic tables for explicit, observable incremental processing.&lt;/p&gt;
&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Phase 1 failed&lt;/strong&gt;: Glue-generated Iceberg files (32-64MB) or bigger size  caused 5-10x slower Snowflake queries&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Phase 2 wins&lt;/strong&gt;: Snowflake-managed Iceberg auto-compacts to 256-512MB, 2-5x faster, ~35% cost savings&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Dynamic Tables gotcha&lt;/strong&gt;: Cannot clone, opaque refresh timing—unusable for production DevOps&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Solution&lt;/strong&gt;: Explicit Streams + Tasks on log tables—boring, debuggable, production-ready&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Key decision&lt;/strong&gt;: Data team owns Bronze→Silver only; business owns Gold (saved endless remodeling debates)&lt;/li&gt;
&lt;li&gt;💰 &lt;strong&gt;Reality check&lt;/strong&gt;: "Cloud-neutral" = readable across engines, not free migration&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Our Team Context (Yours Will Differ)
&lt;/h2&gt;

&lt;p&gt;Before diving into architecture decisions, here's who we are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt;: Strong SQL/dbt, limited Spark/Scala experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priorities&lt;/strong&gt;: Ship fast, avoid operational black boxes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraint&lt;/strong&gt;: No dedicated DevOps for Glue cluster tuning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This shaped every decision below.&lt;/strong&gt; A team fluent in Spark would have made different trade-offs.&lt;/p&gt;


&lt;h2&gt;
  
  
  🎯 The Goal
&lt;/h2&gt;

&lt;p&gt;Unify data from SAP HANA (change data capture), Salesforce Data Cloud, and raw event streams into a single &lt;strong&gt;cloud-neutral lakehouse&lt;/strong&gt;—no proprietary lock-in, full cross-tool interoperability.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Iceberg?
&lt;/h3&gt;

&lt;p&gt;We evaluated Delta Lake, Apache Hudi, and Apache Iceberg a &lt;strong&gt;couple of years ago&lt;/strong&gt;. Here's the comprehensive comparison that drove our decision:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Iceberg&lt;/th&gt;
&lt;th&gt;Delta Lake&lt;/th&gt;
&lt;th&gt;Hudi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Partition Evolution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Change without rewrite&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;❌ Requires full table rewrite&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Native support&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;td&gt;✅ Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automated Compaction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;MAINTAIN ICEBERG TABLE&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;⚠️ Manual (OPTIMIZE tuning)&lt;/td&gt;
&lt;td&gt;⚠️ Manual/semi-auto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schema Evolution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Full (add/drop/rename/reorder)&lt;/td&gt;
&lt;td&gt;✅ Add/drop columns&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Engine Compatibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Spark, Trino, Flink, Snowflake, Dremio&lt;/td&gt;
&lt;td&gt;⚠️ Spark-first, limited others&lt;/td&gt;
&lt;td&gt;⚠️ Spark-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Platform Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ AWS Glue/Athena, Azure, GCP, Snowflake&lt;/td&gt;
&lt;td&gt;✅ AWS, Azure (Fabric native), Databricks&lt;/td&gt;
&lt;td&gt;⚠️ AWS, limited Azure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File Format Flexibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Parquet, ORC, Avro&lt;/td&gt;
&lt;td&gt;⚠️ Parquet only&lt;/td&gt;
&lt;td&gt;✅ Parquet, ORC, Avro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Community Governance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Apache Foundation (vendor-neutral)&lt;/td&gt;
&lt;td&gt;⚠️ Databricks-controlled&lt;/td&gt;
&lt;td&gt;✅ Apache Foundation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Why We Chose Iceberg (and Ruled Out Delta/Hudi)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Iceberg's winning factors for our context:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Partition evolution without rewrites&lt;/strong&gt;: Our SAP data's partitioning strategy evolved over time (daily → monthly as data matured). Iceberg lets us change partition specs without rewriting billions of rows. Delta requires full table rewrite—a multi-day, multi-TB operation we couldn't afford.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Snowflake-managed support&lt;/strong&gt;: Only Iceberg offers native managed tables in Snowflake Horizon Catalog with automatic compaction. Delta/Hudi would lock us into external table limitations with the Phase 1 performance issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automated compaction&lt;/strong&gt;: Snowflake's &lt;code&gt;MAINTAIN ICEBERG TABLE&lt;/code&gt; handles file optimization automatically. Delta requires manual compaction tuning in Spark—expertise our SQL-first team doesn't have.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Primary key enforcement&lt;/strong&gt;: Iceberg supports primary keys natively, critical for our SAP source data integrity (customer IDs, order numbers). Delta lacks this—you must enforce it in application logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vendor-neutral governance&lt;/strong&gt;: Apache Foundation stewardship means no single vendor controls the spec. Delta's governance is tied to Databricks' business interests.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Bottom line&lt;/strong&gt;: Iceberg was the &lt;strong&gt;safe bet for multi-engine flexibility&lt;/strong&gt; without vendor lock-in. If you're Azure-only with Fabric, Delta is pragmatic. If you're Databricks-native, Delta is the path of least resistance. But for AWS + Snowflake + future optionality, Iceberg was the only choice.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Snowflake for Compute?
&lt;/h3&gt;

&lt;p&gt;Three differentiators that closed the decision:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Separation of Storage &amp;amp; Compute&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Scale workloads independently to meet business demands while enabling detailed chargeback per team or domain—without disrupting other workloads. → &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/understanding-snowflake-virtual-warehouses-4p5l"&gt;Deep dive: Understanding Snowflake Virtual Warehouses&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Automatic Caching + Smart Pruning (RELY Operators)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Sub-second query performance on petabyte-scale data through intelligent result/metadata caching and constraint-based optimization. → &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/how-snowflakes-rely-constraint-supercharges-your-star-schema-queries-3n4f"&gt;Deep dive: RELY Constraint for Star-Schema Queries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Compute Billing Precision&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;POC result&lt;/strong&gt;: Our thousands of BI dashboards were only charged for calculation time—not data transfer to BI tools. &lt;strong&gt;Estimated 40–60% cost savings&lt;/strong&gt; vs. **competitors **billing full query duration.&lt;/p&gt;


&lt;h2&gt;
  
  
  🏛️ Medallion Architecture
&lt;/h2&gt;

&lt;p&gt;Our lakehouse follows the classic three-layer medallion model, tailored for SAP source systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                    DATA SOURCES                             │
│   SAP HANA (CDC)  │  Salesforce Data Cloud  │  Raw Streams │
└────────────┬──────────────────┬─────────────────────┬───────┘
             │                  │                     │
             ▼                  ▼                     ▼
┌─────────────────────────────────────────────────────────────┐
│  🥉 BRONZE LAYER — AWS Glue Spark → Iceberg writes to S3   │
│  Raw ingestion, no transformation, full fidelity            │
└─────────────────────────┬───────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  🥈 SILVER LAYER — Iceberg Tables (AWS-hosted, S3)         │
│  Matches source table structures                            │
│  e.g., KNA1 (Customers), MARA (Materials), VBAK (Orders)   │
└─────────────────────────┬───────────────────────────────────┘
                          │  Stream on log_table
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  🥇 GOLD LAYER — Snowflake-Managed Iceberg (Horizon Cat.)  │
│  Proper dimensions &amp;amp; facts with business names             │
│  e.g., DIM_CUSTOMER, FACT_SALES_ORDER, DIM_PRODUCT         │
└─────────────────────────┬───────────────────────────────────┘
                          │
                          ▼
              BI Tools / ML / Fabric Export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sounds ideal?&lt;/strong&gt; Early reality: Snowflake choked on our Glue-generated files.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture Decision Nobody Talks About: Layer Ownership
&lt;/h2&gt;

&lt;p&gt;Here's what we learned the hard way: &lt;strong&gt;Don't own Gold if you don't have to.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this boundary matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Silver = Source truth&lt;/strong&gt;: Matches SAP table structures (KNA1, MARA, VBAK). Data engineering controls quality, structure, and change tracking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gold = Business semantics&lt;/strong&gt;: DIM_CUSTOMER, FACT_SALES_ORDER. Business teams decide how to model, aggregate, and interpret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundary = Contract&lt;/strong&gt;: Silver provides clean, change-tracked source data with explicit SLAs; business teams own downstream transformations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  This Decision Saved Us From:
&lt;/h3&gt;

&lt;p&gt;❌ Endless "why did the customer count change?" debates (business definition shifts, not data quality issues)&lt;br&gt;&lt;br&gt;
❌ Remodeling dimensions every quarter when business logic evolves&lt;br&gt;&lt;br&gt;
❌ Being the bottleneck for every dashboard request&lt;br&gt;&lt;br&gt;
❌ Owning interpretations of business rules we don't fully understand  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your team may differ&lt;/strong&gt;, but &lt;strong&gt;define the ownership boundary early&lt;/strong&gt; or you'll own every downstream interpretation forever. The tools (dbt, Iceberg, Snowflake) don't enforce this—you must.&lt;/p&gt;


&lt;h2&gt;
  
  
  Phase 1: Glue-Managed Iceberg + Snowflake External Tables ❌
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Performance Trap
&lt;/h3&gt;

&lt;p&gt;We started simple: Spark jobs in Glue created Iceberg tables stored in Glue Catalog; Snowflake linked them as external Iceberg tables.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Broke: File Size Mismatch
&lt;/h3&gt;

&lt;p&gt;Snowflake's Iceberg scanner is optimized for specific file characteristics per &lt;a href="https://docs.snowflake.com/en/user-guide/tables-external-intro#general-file-sizing-recommendations" rel="noopener noreferrer"&gt;official recommendations&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Snowflake Recommendation&lt;/th&gt;
&lt;th&gt;What Glue Produced&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File size&lt;/td&gt;
&lt;td&gt;256 – 512 MB&lt;/td&gt;
&lt;td&gt;32 – 64 MB (many small files)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row group size&lt;/td&gt;
&lt;td&gt;16 – 256 MB&lt;/td&gt;
&lt;td&gt;&amp;lt; 16 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row groups per file&lt;/td&gt;
&lt;td&gt;Multiple (for parallelism)&lt;/td&gt;
&lt;td&gt;Often 1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The result&lt;/strong&gt;: Full table scans instead of pruned reads, query times &lt;strong&gt;5–10x slower&lt;/strong&gt; than expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────┐
│              PHASE 1 ARCHITECTURE                    │
│                                                      │
│  AWS Glue Spark ──writes──▶ Iceberg (Glue Catalog)  │
│                                    │                 │
│                             S3 Parquet files         │
│                          (small, fragmented)         │
│                                    │                 │
│  Snowflake ◀──external table──────┘                 │
│  (slow scans, no auto-compaction)                    │
└──────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔀 Decision Point: Invest in Spark Tuning or Snowflake-Managed?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Option A&lt;/strong&gt;: Hire Spark expertise, tune file compaction settings  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeline: 3-6 months
&lt;/li&gt;
&lt;li&gt;Ongoing cost: Maintain Spark expertise, monitor file sizes
&lt;/li&gt;
&lt;li&gt;Risk: Our team lacks Spark internals experience
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option B&lt;/strong&gt;: Snowflake-managed Iceberg via Horizon Catalog  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeline: 2 weeks
&lt;/li&gt;
&lt;li&gt;Ongoing cost: Snowflake MAINTAIN ICEBERG TABLE compute
&lt;/li&gt;
&lt;li&gt;Upside: Handles compaction automatically, team stays in SQL/dbt comfort zone
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;We chose Option B&lt;/strong&gt;: Our team's strength is SQL/dbt, not Spark internals. Let Snowflake handle the file lifecycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: Snowflake-Managed Iceberg + Horizon Catalog ✅
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Pivot
&lt;/h3&gt;

&lt;p&gt;Snowflake-managed Iceberg tables put Snowflake in charge of the table lifecycle on your S3 bucket—Horizon Catalog governs metadata, access, and interoperability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────┐
│               PHASE 2 ARCHITECTURE                       │
│                                                          │
│  AWS Glue Spark ──writes──▶ Snowflake Horizon Catalog   │
│                                    │                     │
│                    Horizon manages Iceberg metadata      │
│                    Auto-compaction to 256-512MB files    │
│                                    │                     │
│                             S3 (your bucket)             │
│                          Optimal Parquet layout          │
│                                    │                     │
│  Snowflake ◀──native read─────────┘                     │
│  (2–5x faster, full pruning, cloneable*)                 │
│                                                          │
│  BI Tools ◀── Snowflake compute                         │
│  Glue/Spark ◀── Iceberg open format (bidirectional)     │
└──────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wins
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;2–5x query speedup&lt;/strong&gt; vs. Phase 1 external tables&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Automatic compaction&lt;/strong&gt; to optimal file sizes via &lt;code&gt;MAINTAIN ICEBERG TABLE&lt;/code&gt;&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Bidirectional access&lt;/strong&gt;: Glue/Spark can write, Snowflake reads natively; BI tools use Snowflake compute&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Open format preserved&lt;/strong&gt;: Gold layer exportable to Fabric/Polaris later  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt;: Snowflake compute is billed for maintenance runs—but total ops cost is lower than Phase 1's slow queries burning warehouse credits.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚠️ Why We Abandoned Dynamic Tables for Production
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This section describes our original evaluation. See the September 2026 update above for current capabilities.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dynamic Tables on Iceberg sounded perfect—zero-code pipelines, automatic refresh. We hit two walls that forced us back to explicit patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 1 — No Cloning Support:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- This FAILS silently on dynamic Iceberg tables&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;prod_clone&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;prod_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Dynamic Iceberg tables are simply skipped in the clone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DB/schema clones skip dynamic Iceberg tables entirely.&lt;/strong&gt; This is a DevOps killer—no dev/test environment parity, no blue-green deploys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 2 — Opaque Refresh Timing:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Incremental refresh latency was unpredictable and nearly impossible to debug for SLA enforcement. Monitoring refresh lag and debugging failures was guesswork with no visibility into what triggered refreshes or why they were delayed.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔀 Trade-off: Zero-Code vs. Zero-Surprise
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dynamic Tables promise automation but hide:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When refreshes actually run&lt;/li&gt;
&lt;li&gt;What triggered the refresh&lt;/li&gt;
&lt;li&gt;How to debug failures in production at 3am&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Streams + Tasks = More code, but debuggable in production.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture decision-makers&lt;/strong&gt;: Optimize for production support, not dev convenience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution&lt;/strong&gt;: Abandoned dynamic tables for Silver → Gold. Back to explicit Streams + Tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our Solution: Log Table Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;┌─────────────────────────────────────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;           &lt;span class="n"&gt;SILVER&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;GOLD&lt;/span&gt; &lt;span class="n"&gt;PIPELINE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                                                      &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="n"&gt;Silver&lt;/span&gt; &lt;span class="n"&gt;Layer&lt;/span&gt;                                        &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="err"&gt;│&lt;/span&gt;                                               &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="err"&gt;├──▶&lt;/span&gt; &lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log_table&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracks&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;│&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;▼&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;STREAM&lt;/span&gt; &lt;span class="n"&gt;log_changes&lt;/span&gt;                       &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="err"&gt;◀──&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;│&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;▼&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TASK&lt;/span&gt; &lt;span class="n"&gt;gold_refresh&lt;/span&gt;            &lt;span class="err"&gt;◀──&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;       &lt;span class="n"&gt;SCHEDULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'5 MINUTE'&lt;/span&gt;                         &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;MERGE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_360&lt;/span&gt;               &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;          &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;log_changes&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;                     &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;│&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                &lt;span class="err"&gt;▼&lt;/span&gt;                                    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="n"&gt;Gold&lt;/span&gt; &lt;span class="n"&gt;Layer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Snowflake&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Managed&lt;/span&gt; &lt;span class="n"&gt;Iceberg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└─────────────────────────────────────────────────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The explicit, cloneable, debuggable pattern&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;STREAM&lt;/span&gt; &lt;span class="n"&gt;log_changes&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TASK&lt;/span&gt; &lt;span class="n"&gt;gold_refresh&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compute_xs&lt;/span&gt;
  &lt;span class="n"&gt;SCHEDULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'5 MINUTE'&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;STREAM_HAS_DATA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'log_changes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AS&lt;/span&gt;
  &lt;span class="n"&gt;MERGE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_360&lt;/span&gt; &lt;span class="n"&gt;tgt&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;log_changes&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INSERT'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;tgt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;MATCHED&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;MATCHED&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Wins:
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Full cloning support&lt;/strong&gt; (dev/prod parity restored)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Transparent costs&lt;/strong&gt; — every execution logged in TASK_HISTORY&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Debuggable&lt;/strong&gt; — stream offset visible, failures isolated&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Fabric-friendly&lt;/strong&gt; — Gold can be exported as Iceberg/Parquet later&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Cross-Platform Reality: Why We Can (and Can't) Pivot
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Current State: AWS + Snowflake, Iceberg on S3
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Open format preserved&lt;/strong&gt;: Can read Iceberg from Spark, Trino, Athena&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Gold exportable&lt;/strong&gt;: Stream to Parquet → Fabric mirroring works&lt;br&gt;&lt;br&gt;
⚠️ &lt;strong&gt;Fabric constraint&lt;/strong&gt;: OneLake wants Delta + same Azure region for zero-copy&lt;br&gt;&lt;br&gt;
⚠️ &lt;strong&gt;Horizon lock-in&lt;/strong&gt;: Snowflake-managed Iceberg metadata tied to Horizon Catalog  &lt;/p&gt;

&lt;h3&gt;
  
  
  What "Cloud-Neutral" Actually Means
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Not&lt;/strong&gt;: "Deploy anywhere tomorrow with zero effort"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Actually&lt;/strong&gt;: "Readable by multiple engines, movable with effort"&lt;/p&gt;

&lt;h3&gt;
  
  
  If We Had to Migrate to Fabric:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Migration Effort&lt;/th&gt;
&lt;th&gt;Estimated Timeline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bronze → Silver ingestion&lt;/td&gt;
&lt;td&gt;Rewrite Glue jobs to Delta&lt;/td&gt;
&lt;td&gt;2-3 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silver → Gold dbt models&lt;/td&gt;
&lt;td&gt;Port to Fabric SQL (syntax diffs)&lt;/td&gt;
&lt;td&gt;1-2 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gold Iceberg tables&lt;/td&gt;
&lt;td&gt;Export as Parquet, re-create in Fabric Warehouse&lt;/td&gt;
&lt;td&gt;1-2 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────┐
│                  CATALOG LANDSCAPE                         │
│                                                            │
│  Our Setup: AWS us-east-1                                  │
│  ┌────────────────────────────────────────────────────┐   │
│  │  Snowflake Horizon Catalog (current)               │   │
│  │  + AWS Glue Catalog (bronze/silver ingestion)      │   │
│  └────────────────────────────────────────────────────┘   │
│                                                            │
│  Future Options:                                           │
│  ┌─────────────────┐    ┌──────────────────────────────┐  │
│  │ Polaris Catalog │    │ Microsoft Fabric OneLake      │  │
│  │ (Snowflake SaaS)│    │ (wants Delta; Azure-region   │  │
│  │ Maturing fast   │    │  only for zero-copy)          │  │
│  └─────────────────┘    └──────────────────────────────┘  │
│                                                            │
│  Gold Streams → Parquet export → Fabric compatible ✅      │
└────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Our Decision:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;We prioritized Snowflake ecosystem depth over day-1 multi-cloud portability.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Iceberg gave us an &lt;strong&gt;exit path, not a free exit&lt;/strong&gt;. For teams needing Fabric OneLake zero-copy, starting with Delta on Azure is the pragmatic choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  💰 Cost Reality Check
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 (Glue + External Iceberg):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Glue: costs to write and maintain iceberg tables&lt;/li&gt;
&lt;li&gt;Snowflake query costs: 3x higher due to full scans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 (Snowflake-managed Iceberg):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MAINTAIN ICEBERG TABLE: in warehouse credits&lt;/li&gt;
&lt;li&gt;Query costs: 60% reduction (pruning + caching works correctly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Net savings: 35% monthly&lt;/strong&gt; vs. Phase 1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Takeaway&lt;/strong&gt;: Optimize for query performance where your users actually spend time, not just ingestion costs.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔑 Lessons for Your Lakehouse
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. File sizes first
&lt;/h3&gt;

&lt;p&gt;Target 256-512MB Parquets or let Snowflake &lt;code&gt;MAINTAIN ICEBERG TABLE&lt;/code&gt; handle it automatically. Small files kill Snowflake performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Clone-test early
&lt;/h3&gt;

&lt;p&gt;Dynamic Iceberg tables are powerful but not clone-safe for databases/schemas. Test your DevOps workflow before committing to production.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Horizon bidirectional is a game-changer
&lt;/h3&gt;

&lt;p&gt;If your team uses both Spark and Snowflake, Horizon gives you the best of both without choosing sides. Write with Spark, read with Snowflake—all on the same Iceberg tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Streams &amp;gt; automation black boxes
&lt;/h3&gt;

&lt;p&gt;Explicit Streams + Tasks always win in production ops: observable at 3am, debuggable from logs, cloneable for dev/test.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Phase your migration
&lt;/h3&gt;

&lt;p&gt;Don't try to migrate all layers at once. Start with Gold (highest query frequency), validate performance, then move Silver.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Define ownership boundaries early
&lt;/h3&gt;

&lt;p&gt;Bronze→Silver→Gold isn't just technical layers—it's organizational boundaries. Decide who owns what before the first production table.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use What: Decision Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your Situation&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Team strong in Spark, need multi-engine&lt;/td&gt;
&lt;td&gt;Glue-managed Iceberg + External tables&lt;/td&gt;
&lt;td&gt;Keep expertise where it is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team SQL-first, Snowflake primary engine&lt;/td&gt;
&lt;td&gt;Snowflake-managed Iceberg&lt;/td&gt;
&lt;td&gt;Let Snowflake handle file lifecycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Must support Fabric OneLake zero-copy&lt;/td&gt;
&lt;td&gt;Delta Lake on Azure&lt;/td&gt;
&lt;td&gt;Iceberg works but not zero-copy on Fabric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need dev/prod clones + SLA guarantees&lt;/td&gt;
&lt;td&gt;Streams + Tasks (avoid Dynamic Tables)&lt;/td&gt;
&lt;td&gt;Observable, debuggable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bronze/Silver only (like us)&lt;/td&gt;
&lt;td&gt;dbt incremental + Developer Toolkit&lt;/td&gt;
&lt;td&gt;Explicit watermark control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bottom line&lt;/strong&gt;: Cloud-neutral means readable across tools, not free migration. Choose the platform that matches your team's strengths, use open formats for portability insurance.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next for Us
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Investigate Polaris Catalog or wait for Horizon Catalog for true multi-vendor Iceberg metadata management&lt;/li&gt;
&lt;li&gt;Evaluate cost/performance of streaming directly from Kafka → Snowflake Iceberg&lt;/li&gt;
&lt;li&gt;Explore Iceberg v3 features for enhanced BCDR and CDC capabilities&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🆕 Update: Iceberg v3 Features (March 2026) That Could Change Your Decision
&lt;/h2&gt;

&lt;p&gt;Since our original evaluation, &lt;strong&gt;Snowflake released Apache Iceberg v3 support in public preview (March 2026)&lt;/strong&gt; with capabilities that address several of our pain points and unlock new architectural patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's New in Iceberg v3
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Cross-Region Replication for Snowflake-Managed Tables&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it enables&lt;/strong&gt;: BCDR failover and replication groups for Iceberg tables across regions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it matters&lt;/strong&gt;: Previously, our DR strategy required complex Parquet exports. Now, Snowflake-managed Iceberg tables can replicate with full consistency (including row lineage and deletion vectors)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture impact&lt;/strong&gt;: We can now deploy active-passive DR without custom tooling&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://docs.snowflake.com/en/user-guide/tables-iceberg-configure-replication" rel="noopener noreferrer"&gt;Replication Config Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Catalog-Linked Databases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it enables&lt;/strong&gt;: Connect to remote Iceberg catalogs (AWS Glue, Polaris, etc.) with automatic namespace discovery and read/write support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it matters&lt;/strong&gt;: Our Phase 1 external table limitations are eliminated—we can now write back to Glue Catalog-managed tables from Snowflake&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture impact&lt;/strong&gt;: True bidirectional catalog federation; could unblock our "Glue as ingestion, Snowflake as analytics" hybrid&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://docs.snowflake.com/en/sql-reference/sql/create-database-catalog" rel="noopener noreferrer"&gt;CREATE Catalog-Linked Database Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Which Features Would Have Changed Our Decision?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Would have stayed with our choice (Iceberg + Horizon):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Cross-region replication&lt;/strong&gt; validates our bet on Snowflake-managed Iceberg (Delta still doesn't have this)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Catalog-linked databases&lt;/strong&gt; eliminate the Phase 1 external table pain without abandoning Glue ingestion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bottom Line: Iceberg v3 Strengthens the "Safe Bet"
&lt;/h3&gt;

&lt;p&gt;For SQL-first teams on Snowflake, &lt;strong&gt;Iceberg v3 eliminates the last major operational friction points&lt;/strong&gt; we encountered. The combination of Horizon Catalog + v3 features delivers the "cloud-neutral with vendor optimization" balance we were seeking.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your biggest Iceberg-on-Snowflake headache?&lt;/strong&gt; Drop it below 👇&lt;/p&gt;

&lt;h1&gt;
  
  
  Snowflake #ApacheIceberg #Lakehouse #DataEngineering #HorizonCatalog #DataArchitecture #AWS #OpenLakehouse
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>dataengineering</category>
      <category>snowflake</category>
    </item>
    <item>
      <title>Part 4: Clone ++ Parallelization and Production Features</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Thu, 30 Apr 2026 13:58:45 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/part-4-parallelization-and-production-features-3b3d</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/part-4-parallelization-and-production-features-3b3d</guid>
      <description>&lt;p&gt;&lt;strong&gt;Previously:&lt;/strong&gt; In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9"&gt;Part 3&lt;/a&gt;, we automated permission management with dynamic RBAC provisioning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this post:&lt;/strong&gt; Scale your cloning operations with parallel processing, add resume-from-failure capabilities, implement audit logging, and build production-grade orchestration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Performance Problem
&lt;/h2&gt;

&lt;p&gt;Our repointing solution works, but doesn't scale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Sequential processing (6 schemas)&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ADMIN'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;-- 5 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'INTEGRATION'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;-- 8 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GOLD'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;-- 12 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'SILVER'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;-- 10 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PLATINUM'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;-- 7 min&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ARCHIVE'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;-- 3 min&lt;/span&gt;

&lt;span class="c1"&gt;-- Total: 45 minutes ⏰&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Each schema blocks the next. We're not using Snowflake's compute parallelism.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution: ASYNC/AWAIT Pattern
&lt;/h2&gt;

&lt;p&gt;Snowflake's &lt;code&gt;ASYNC&lt;/code&gt; and &lt;code&gt;AWAIT&lt;/code&gt; keywords enable parallel execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Launch all schemas in parallel&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ADMINISTRATION'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'INTEGRATION'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DATA'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'REPORTING'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'prod_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ARCHIVE'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;-- Wait for all to complete&lt;/span&gt;
&lt;span class="n"&gt;AWAIT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Result: ~12 minutes (limited by slowest schema)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Speedup:&lt;/strong&gt; 45 minutes → 12 minutes = &lt;strong&gt;73% faster&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Parallel Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  High-Level Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SP_REPOINT_PARALLEL (Orchestrator)
├─ Get all schemas in clone
├─ For each schema:
│  └─ ASYNC (SP_REPOINT_SCHEMA_AND_LOG)
├─ AWAIT ALL
└─ Aggregate results

SP_REPOINT_SCHEMA_AND_LOG (Logging Wrapper)
├─ Call SP_REPOINT_SCHEMA
├─ Capture result
└─ Insert into temp results table

SP_REPOINT_SCHEMA (Worker)
├─ Repoint views
├─ Repoint procedures
├─ Repoint functions
├─ Repoint tasks
└─ Return JSON result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Orchestrator Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Simplified orchestrator logic&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="n"&gt;sp_clone_repoint_parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;source_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="c1"&gt;-- Create temp table for results&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;temp_repoint_results&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;schema_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="n"&gt;VARIANT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;-- Get all schemas&lt;/span&gt;
    &lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="n"&gt;schema_rs&lt;/span&gt; &lt;span class="n"&gt;RESULTSET&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SCHEMA_NAME&lt;/span&gt; 
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFORMATION_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SCHEMATA&lt;/span&gt; 
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA_NAME&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'INFORMATION_SCHEMA'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;-- Launch parallel workers&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;schema_rs&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt;
        &lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_repoint_schema_and_log&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;clone_db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;source_db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;SCHEMA_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Wait for all workers&lt;/span&gt;
    &lt;span class="n"&gt;AWAIT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Aggregate results&lt;/span&gt;
    &lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="n"&gt;final_result&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;OBJECT_CONSTRUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'parallel_schemas'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'total_duration_seconds'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'second'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="s1"&gt;'schema_results'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ARRAY_AGG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;temp_repoint_results&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;final_result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why temp tables?&lt;/strong&gt; ASYNC procedures can't return values directly. We collect results in a temp table visible to the orchestrator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_repoint_parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTION_DB'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="c1"&gt;-- {&lt;/span&gt;
&lt;span class="c1"&gt;--   "parallel_schemas": 6,&lt;/span&gt;
&lt;span class="c1"&gt;--   "total_duration_seconds": 720,  -- 12 minutes&lt;/span&gt;
&lt;span class="c1"&gt;--   "schema_results": [&lt;/span&gt;
&lt;span class="c1"&gt;--     {"schema": "ADMINISTRATION", "views_fixed": 12, "procedures_fixed": 8},&lt;/span&gt;
&lt;span class="c1"&gt;--     {"schema": "ANALYTICS", "views_fixed": 98, "procedures_fixed": 42},&lt;/span&gt;
&lt;span class="c1"&gt;--     ...&lt;/span&gt;
&lt;span class="c1"&gt;--   ]&lt;/span&gt;
&lt;span class="c1"&gt;-- }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Same Pattern for Streams
&lt;/h2&gt;

&lt;p&gt;Parallel stream recreation follows identical architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Orchestrator launches per-schema stream workers&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="n"&gt;sp_clone_recreate_streams_parallel&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;temp_stream_results&lt;/span&gt; &lt;span class="p"&gt;(...);&lt;/span&gt;

    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt; &lt;span class="k"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_recreate_streams_schema_and_log&lt;/span&gt;&lt;span class="p"&gt;(...));&lt;/span&gt;

    &lt;span class="n"&gt;AWAIT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;aggregated_results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Resume-from-Failure: Step-Based Tracking
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Cloning is multi-step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Delete old RBAC mappings&lt;/li&gt;
&lt;li&gt;Clone database&lt;/li&gt;
&lt;li&gt;Revoke production grants&lt;/li&gt;
&lt;li&gt;Repoint objects&lt;/li&gt;
&lt;li&gt;Recreate streams&lt;/li&gt;
&lt;li&gt;Create new roles&lt;/li&gt;
&lt;li&gt;Apply RBAC mappings&lt;/li&gt;
&lt;li&gt;Transfer ownership&lt;/li&gt;
&lt;li&gt;Suspend tasks&lt;/li&gt;
&lt;li&gt;Validate clone&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What happens if Step 5 fails?&lt;/strong&gt; You don't want to start over!&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Step Logging
&lt;/h3&gt;

&lt;p&gt;Track each step in a dedicated table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;clone_step_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;log_id&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;audit_id&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- Links to clone_audit_log&lt;/span&gt;
    &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;step_number&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;step_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'PENDING'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- PENDING, IN_PROGRESS, SUCCESS, FAILED&lt;/span&gt;
    &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="n"&gt;VARIANT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;started_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Logging Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In master clone procedure&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stepNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stepName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stepFunction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Log step start&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO clone_step_log &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(audit_id, clone_db, step_number, step_name, status) &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VALUES (..., &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;stepNum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;stepName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;', 'IN_PROGRESS')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Execute the step&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stepFunction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Log success&lt;/span&gt;
        &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE clone_step_log &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SET status = 'SUCCESS', result = '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;', &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;    completed_at = CURRENT_TIMESTAMP() &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE step_number = &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;stepNum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; AND status = 'IN_PROGRESS'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Log failure&lt;/span&gt;
        &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE clone_step_log &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SET status = 'FAILED', result = OBJECT_CONSTRUCT('error', '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'), &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;    completed_at = CURRENT_TIMESTAMP() &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE step_number = &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;stepNum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; AND status = 'IN_PROGRESS'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Re-throw to abort remaining steps&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resume Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Master procedure signature&lt;/span&gt;
&lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="nx"&gt;PROCEDURE&lt;/span&gt; &lt;span class="nf"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;clone_type&lt;/span&gt; &lt;span class="nx"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;name_part1&lt;/span&gt; &lt;span class="nx"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;name_part2&lt;/span&gt; &lt;span class="nx"&gt;VARCHAR&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT&lt;/span&gt; &lt;span class="nx"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;resume_from_step&lt;/span&gt; &lt;span class="nx"&gt;FLOAT&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;👈&lt;/span&gt; &lt;span class="nx"&gt;Resume&lt;/span&gt; &lt;span class="nx"&gt;parameter&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Execution logic&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE_RBAC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deleteRBACMappings&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CLONE_DATABASE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloneDatabase&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REVOKE_GRANTS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;revokeGrants&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REPOINT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repointParallel&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STREAMS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recreateStreamsParallel&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CREATE_ROLES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createRoles&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;APPLY_RBAC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;applyRBAC&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ICEBERG&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handleIceberg&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUSPEND_TASKS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;suspendTasks&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VALIDATE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validateClone&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;resume_from_step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Skip this step&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;executeStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resuming
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Initial attempt fails at step 5&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- ERROR at step 5: Stream recreation failed&lt;/span&gt;

&lt;span class="c1"&gt;-- Check what happened&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_step_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DEV_ANALYTICS_DB'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Fix the issue, then resume from step 5&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Steps 1-4 skipped, execution resumes from step 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key benefit:&lt;/strong&gt; No need to wait another 30 minutes to re-clone. Just fix and resume.&lt;/p&gt;




&lt;h2&gt;
  
  
  Audit Logging: Observability
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Audit Table
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;clone_audit_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;audit_id&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;clone_type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- PROJECT, RELEASE&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- CREATE, DROP, UPDATE&lt;/span&gt;
    &lt;span class="n"&gt;project_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;env_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;-- DEV, QA, STAGING&lt;/span&gt;
    &lt;span class="n"&gt;source_db&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTION_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- IN_PROGRESS, SUCCESS, FAILED&lt;/span&gt;
    &lt;span class="n"&gt;error_msg&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;created_by&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_USER&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Metrics
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Clone success rate (last 30 days)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_clones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SUCCESS'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;successful&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;successful&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_clones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;success_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_audit_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CREATE'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Average duration by environment&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;env_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'minute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_minutes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_audit_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SUCCESS'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;env_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Most common failure points&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;step_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;failure_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clone_step_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'FAILED'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;started_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;step_name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Task Suspension: Cost Control
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Cloned databases inherit &lt;strong&gt;active tasks&lt;/strong&gt; from production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;TASKS&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Result: 23 tasks, STATE = 'started' ⚠️&lt;/span&gt;
&lt;span class="c1"&gt;-- Running hourly, daily, etc. in DEV!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; $200-500/month per clone in wasted compute.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Auto-suspend all tasks in clone&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;suspendTasks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;schemas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAllSchemas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tasksSuspended&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHOW TASKS IN SCHEMA &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;started&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALTER TASK &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; SUSPEND&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;tasksSuspended&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;tasks_suspended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tasksSuspended&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Integration:&lt;/strong&gt; Add as Step 9 in clone pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; All tasks suspended by default in non-prod clones.&lt;/p&gt;




&lt;h2&gt;
  
  
  Iceberg Table Handling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Auto-Grant Volume Access
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 8: Handle Iceberg tables&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleIcebergTables&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;icebergTables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT DISTINCT external_volume &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM information_schema.tables &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE table_type IN ('ICEBERG TABLE', 'DYNAMIC ICEBERG TABLE') &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AND external_volume IS NOT NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;icebergTables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRANT USAGE ON EXTERNAL VOLUME &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; TO DATABASE &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;volume_grants&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;volume_grants_applied&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;volume_grants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Master Orchestration
&lt;/h2&gt;

&lt;p&gt;Bringing it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- One command to rule them all&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Behind the scenes:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: DELETE_RBAC_MAPPINGS      [3 sec]
Step 2: CLONE_DATABASE             [3 sec]  ← Snowflake native
Step 3: REVOKE_GRANTS              [45 sec]
Step 4: REPOINT_PARALLEL           [8 min]  ← ASYNC/AWAIT
Step 5: RECREATE_STREAMS_PARALLEL  [2 min]  ← ASYNC/AWAIT
Step 6: CREATE_ROLES               [1 min]
Step 7: APPLY_RBAC_MAPPINGS        [15 sec]
Step 8: HANDLE_ICEBERG             [5 sec]
Step 9: SUSPEND_TASKS              [10 sec]
Step 10: VALIDATE                  [30 sec]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total: ~12 minutes

Result:
✅ Fully functional dev environment
✅ Correct permissions
✅ All references updated
✅ Streams recreated
✅ Tasks suspended
✅ Iceberg configured
✅ Validated and ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance Tuning Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Warehouse Sizing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Use larger warehouse for parallel operations&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;clone_wh&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MEDIUM'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- After clone completes&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="n"&gt;clone_wh&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;WAREHOUSE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SMALL'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Batch Processing
&lt;/h3&gt;

&lt;p&gt;For 50+ schemas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Process in batches to avoid overwhelming warehouse&lt;/span&gt;
&lt;span class="c1"&gt;-- Batch 1: Schemas 1-10&lt;/span&gt;
&lt;span class="c1"&gt;-- Batch 2: Schemas 11-20&lt;/span&gt;
&lt;span class="c1"&gt;-- etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Query Optimization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ❌ Scans entire database&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;view_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%prod_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Filter by schema first&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;view_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%prod_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Production Metrics: The Full Picture
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Manual&lt;/th&gt;
&lt;th&gt;Semi-Auto&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Fully Automated&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to clone&lt;/td&gt;
&lt;td&gt;1-2 days&lt;/td&gt;
&lt;td&gt;4-6 hours&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8-12 minutes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human intervention&lt;/td&gt;
&lt;td&gt;Constant&lt;/td&gt;
&lt;td&gt;Occasional&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error rate&lt;/td&gt;
&lt;td&gt;15-20%&lt;/td&gt;
&lt;td&gt;5-8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&amp;lt;1%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent clones&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2-3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10+&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resume capability&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Full&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per clone&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Low&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit trail&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complete&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parallelization is essential&lt;/strong&gt; - ASYNC/AWAIT delivers 73% speedup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume-from-failure saves hours&lt;/strong&gt; - Step tracking enables smart recovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability matters&lt;/strong&gt; - Audit logs provide accountability and insights&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task suspension prevents waste&lt;/strong&gt; - Auto-suspend saves $200-500/month per clone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iceberg needs attention&lt;/strong&gt; - External volumes and dynamic tables require special handling&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What We've Built
&lt;/h2&gt;

&lt;p&gt;Over this 4-part series, we created a production-grade cloning solution:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Handles permissions&lt;/strong&gt; - Dynamic RBAC provisioning&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Repoints references&lt;/strong&gt; - All object types updated&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Recreates streams&lt;/strong&gt; - With correct offsets&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Processes in parallel&lt;/strong&gt; - 73% faster&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Resumes from failure&lt;/strong&gt; - No starting over&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Logs everything&lt;/strong&gt; - Complete observability&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Suspends tasks&lt;/strong&gt; - Cost control&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Handles Iceberg&lt;/strong&gt; - External volumes and dynamic tables&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Validates results&lt;/strong&gt; - Health checks  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One command:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_create_master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'myproject'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Fully functional dev environment in ~8 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going Further
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clone Scheduling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Weekly QA refresh&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TASK&lt;/span&gt; &lt;span class="n"&gt;refresh_qa_clone&lt;/span&gt;
  &lt;span class="n"&gt;SCHEDULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'USING CRON 0 6 * * 1 America/Los_Angeles'&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
  &lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_clone_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROJECT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'myproject'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'QA'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Self-Service UI
&lt;/h3&gt;

&lt;p&gt;Build a web interface for teams to request/manage clones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Masking
&lt;/h3&gt;

&lt;p&gt;Apply dynamic masking policies after cloning for PII protection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Tracking
&lt;/h3&gt;

&lt;p&gt;Tag clones with cost centers for chargeback.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-Expiration
&lt;/h3&gt;

&lt;p&gt;Drop clones after N days to control costs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Code Repository:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning" rel="noopener noreferrer"&gt;github.com/LALITHASWAROOPK/snowflake_cloning&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blog Series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk"&gt;Part 1: The Problem and the Promise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60"&gt;Part 2: Repointing References and Recreating Streams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9"&gt;Part 3: Solving Permissions and RBAC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - Part 4: Parallelization and Production Features (this post)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Did this help?&lt;/strong&gt; Star the repo and share with your team!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Open an issue on &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning" rel="noopener noreferrer"&gt;GitHub &lt;/a&gt;or comment below.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9"&gt;Part 3: Solving Permissions and RBAC in Cloned Databases&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Series Start:&lt;/strong&gt; &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk"&gt;Introduction and Overview&lt;/a&gt;&lt;/p&gt;




</description>
      <category>automation</category>
      <category>database</category>
      <category>sql</category>
      <category>snowflake</category>
    </item>
    <item>
      <title>Part 3: Solving Permissions and RBAC in Cloned Databases</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Tue, 28 Apr 2026 14:51:22 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/part-3-solving-permissions-and-rbac-in-cloned-databases-mo9</guid>
      <description>&lt;p&gt;&lt;strong&gt;Previously:&lt;/strong&gt; In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60"&gt;Part 2&lt;/a&gt;, we fixed all the broken database references. But even with correct references, you still can't access anything without proper permissions!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this post:&lt;/strong&gt; Learn how to programmatically manage permissions in cloned databases with dynamic role creation, ownership transfers, and automated RBAC provisioning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Permission Problem (Recap)
&lt;/h2&gt;

&lt;p&gt;After cloning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt; &lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;GRANTS&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| privilege | grantee_name   |
|-----------|----------------|
| OWNERSHIP | PROD_ADMIN     |  ⚠️ Wrong environment!
| USAGE     | PROD_READ_ONLY |  ⚠️ Dev needs different roles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates three immediate problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wrong Roles&lt;/strong&gt; - Production roles shouldn't access dev&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Access&lt;/strong&gt; - Dev team roles aren't granted anything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership Lock&lt;/strong&gt; - Can't modify without PROD_ADMIN privileges&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Solution: Four-Stage Automation
&lt;/h2&gt;

&lt;p&gt;Our approach automates permission management in four stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage 1: Temporary Ownership Transfer
   ↓
Stage 2: Permission Cleanup
   ↓
Stage 3: Dynamic Role Creation
   ↓
Stage 4: RBAC Mapping Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dive into each.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 1: Temporary Ownership Transfer
&lt;/h2&gt;

&lt;p&gt;First, we need control. Grant temporary ownership to a privileged service account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode for understanding&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;clone_database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;SCHEMA&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;SERVICE_ROLE&lt;/span&gt; &lt;span class="nx"&gt;COPY&lt;/span&gt; &lt;span class="nx"&gt;CURRENT&lt;/span&gt; &lt;span class="nx"&gt;GRANTS&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;object_type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TABLES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PROCEDURES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]:&lt;/span&gt;
        &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt; &lt;span class="nx"&gt;object_type&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;SERVICE_ROLE&lt;/span&gt; &lt;span class="nx"&gt;COPY&lt;/span&gt; &lt;span class="nx"&gt;CURRENT&lt;/span&gt; &lt;span class="nx"&gt;GRANTS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; &lt;code&gt;COPY CURRENT GRANTS&lt;/code&gt; preserves existing permissions while changing ownership.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_grant_temp_ownership&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dev_project_db'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="c1"&gt;-- {&lt;/span&gt;
&lt;span class="c1"&gt;--   "schemas_granted": 4,&lt;/span&gt;
&lt;span class="c1"&gt;--   "objects_transferred": 1250,&lt;/span&gt;
&lt;span class="c1"&gt;--   "errors": []&lt;/span&gt;
&lt;span class="c1"&gt;-- }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have control to make changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 2: Permission Cleanup
&lt;/h2&gt;

&lt;p&gt;Revoke production-specific grants, especially &lt;strong&gt;future grants&lt;/strong&gt; that auto-apply to new objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Example: Revoke future ownership from production roles&lt;/span&gt;
&lt;span class="k"&gt;REVOKE&lt;/span&gt; &lt;span class="n"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;FUTURE&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;dev_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt; 
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;PROD_ANALYTICS_OWNER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For multiple schemas and object types, we automate this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;prod_role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source_database&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema_owner_suffix&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;object_type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TABLES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PROCEDURES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]:&lt;/span&gt;
        &lt;span class="nx"&gt;REVOKE&lt;/span&gt; &lt;span class="nx"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;FUTURE&lt;/span&gt; &lt;span class="nx"&gt;object_type&lt;/span&gt; &lt;span class="nx"&gt;IN&lt;/span&gt; &lt;span class="nx"&gt;SCHEMA&lt;/span&gt; 
          &lt;span class="nx"&gt;FROM&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;prod_role&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Stage 3: Dynamic Role Creation
&lt;/h2&gt;

&lt;p&gt;Instead of hardcoding role names, we generate them dynamically based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database name&lt;/strong&gt; (environment-specific)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema name&lt;/strong&gt; (domain-specific)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access level&lt;/strong&gt; (READ, READ_WRITE, ADMIN)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;DATABASE&amp;gt;_&amp;lt;SCHEMA&amp;gt;_&amp;lt;LEVEL&amp;gt;

Examples:
DEV_PROJECT_DB_ANALYTICS_READ
DEV_PROJECT_DB_ANALYTICS_READ_WRITE
DEV_PROJECT_DB_DATA_ADMIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified version for understanding&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;access_level&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;READ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;READ_WRITE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ADMIN&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nx"&gt;role_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clone_db&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;access_level&lt;/span&gt;

        &lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;IF&lt;/span&gt; &lt;span class="nx"&gt;NOT&lt;/span&gt; &lt;span class="nx"&gt;EXISTS&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
        &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;USAGE&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;DATABASE&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
        &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;USAGE&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;SCHEMA&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access_level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;READ&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt; &lt;span class="nx"&gt;TABLES&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
            &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;FUTURE&lt;/span&gt; &lt;span class="nx"&gt;TABLES&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access_level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;READ_WRITE&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DELETE&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt; &lt;span class="nx"&gt;TABLES&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access_level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;ADMIN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt; &lt;span class="nx"&gt;ON&lt;/span&gt; &lt;span class="nx"&gt;SCHEMA&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;role_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this scales:&lt;/strong&gt; From 3 schemas to 300, the pattern stays the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="//../../sql/04_clone_rbac.sql"&gt;sql/04_clone_rbac.sql#L97-L185&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_create_clone_roles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ARRAY_CONSTRUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ADMINISTRATION'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DATA'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="c1"&gt;-- {&lt;/span&gt;
&lt;span class="c1"&gt;--   "roles_created": [&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ADMINISTRATION_READ",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ADMINISTRATION_READ_WRITE",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ADMINISTRATION_ADMIN",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ANALYTICS_READ",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ANALYTICS_READ_WRITE",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_ANALYTICS_ADMIN",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_DATA_READ",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_DATA_READ_WRITE",&lt;/span&gt;
&lt;span class="c1"&gt;--     "DEV_PROJECT_DB_DATA_ADMIN"&lt;/span&gt;
&lt;span class="c1"&gt;--   ],&lt;/span&gt;
&lt;span class="c1"&gt;--   "total_created": 9&lt;/span&gt;
&lt;span class="c1"&gt;-- }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Stage 4: RBAC Mapping
&lt;/h2&gt;

&lt;p&gt;The final piece: map clone-specific roles to &lt;strong&gt;functional roles&lt;/strong&gt; that users actually have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLONE ROLE                           FUNCTIONAL ROLE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DEV_PROJECT_DB_ANALYTICS_READ   →    DATA_ANALYST_ROLE
DEV_PROJECT_DB_ANALYTICS_WRITE  →    DATA_ENGINEER_ROLE  
DEV_PROJECT_DB_ANALYTICS_ADMIN  →    PROJECT_ADMIN_ROLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration Table
&lt;/h3&gt;

&lt;p&gt;Store mappings in a table (not hardcoded):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;rbac_mapping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;schema_role&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;-- Clone-specific role&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;           &lt;span class="c1"&gt;-- DEV, QA, STAGING&lt;/span&gt;
    &lt;span class="n"&gt;functional_role&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;     &lt;span class="c1"&gt;-- User's actual role&lt;/span&gt;
    &lt;span class="k"&gt;operation&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;            &lt;span class="c1"&gt;-- GRANT or REVOKE&lt;/span&gt;
    &lt;span class="n"&gt;execute_indicator&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Example mappings with placeholder&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;rbac_mapping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functional_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{{CLONE_DB}}_ANALYTICS_READ'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DATA_ANALYST_ROLE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GRANT'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{{CLONE_DB}}_ANALYTICS_WRITE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DATA_ENGINEER_ROLE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GRANT'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{{CLONE_DB}}_ANALYTICS_ADMIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PROJECT_ADMIN_ROLE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GRANT'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;{{CLONE_DB}}&lt;/code&gt; is replaced dynamically at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Mapping Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode&lt;/span&gt;
&lt;span class="nx"&gt;mappings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;FROM&lt;/span&gt; &lt;span class="nx"&gt;rbac_mapping&lt;/span&gt; &lt;span class="nx"&gt;WHERE&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target_env&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;mappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;schema_role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{{CLONE_DB}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actual_clone_db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GRANT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;GRANT&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;schema_role&lt;/span&gt; &lt;span class="nx"&gt;TO&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functional_role&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;REVOKE&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;schema_role&lt;/span&gt; &lt;span class="nx"&gt;FROM&lt;/span&gt; &lt;span class="nx"&gt;ROLE&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functional_role&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="//../../sql/04_clone_rbac.sql"&gt;sql/04_clone_rbac.sql#L187-L245&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_apply_rbac_mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Now developers can use their normal roles:&lt;/span&gt;
&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;DATA_ANALYST_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_summary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- ✅ Works!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;Complete permission setup in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_setup_clone_permissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;clone_db&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;source_db&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTION_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'DEV'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Behind the scenes:&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Temporary ownership transferred&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Production grants revoked&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ 9 new environment-specific roles created&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ RBAC mappings applied&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Developers have appropriate access&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Design Principles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Configuration Over Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ❌ Hardcoded in procedures&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;dev_analytics_read&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;data_analyst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Configuration-driven&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;rbac_mapping&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_apply_rbac_mapping&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non-developers can manage permissions&lt;/li&gt;
&lt;li&gt;Different mappings per environment&lt;/li&gt;
&lt;li&gt;Audit trail of changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Dynamic Role Generation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This scales from 3 schemas to 300&lt;/span&gt;
&lt;span class="nx"&gt;role_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;access_level&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Temporary Ownership Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User requests clone
   ↓
Service role takes ownership
   ↓
Service role makes all changes
   ↓
Service role transfers ownership to target roles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Never&lt;/strong&gt; make permission changes as the requesting user.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Future Grants Are Critical
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ❌ Only current objects&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Current AND future objects&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;FUTURE&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common Pitfalls (And How We Avoid Them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pitfall 1: Not Using COPY CURRENT GRANTS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ❌ Drops all existing grants during ownership transfer&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;new_owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Preserves grants during transfer&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;OWNERSHIP&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;new_owner&lt;/span&gt; &lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="n"&gt;GRANTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Our code:&lt;/strong&gt; Always uses &lt;code&gt;COPY CURRENT GRANTS&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall 2: Forgetting Object Types
&lt;/h3&gt;

&lt;p&gt;Don't forget:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DYNAMIC TABLES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ICEBERG TABLES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EVENT TABLES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STAGES&lt;/code&gt;, &lt;code&gt;FILE FORMATS&lt;/code&gt;, &lt;code&gt;SEQUENCES&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Our code:&lt;/strong&gt; Comprehensive object type list in &lt;a href="//../../sql/04_clone_rbac.sql"&gt;&lt;code&gt;sql/04_clone_rbac.sql&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall 3: Not Handling Missing Roles
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Fails if role doesn't exist&lt;/span&gt;
&lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRANT ROLE clone_role TO ROLE functional_role&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Graceful error handling (in our procedures)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRANT ROLE ...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;success_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Our code:&lt;/strong&gt; Try-catch blocks around all grant operations&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Metrics
&lt;/h2&gt;

&lt;p&gt;After implementing automated RBAC:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to grant permissions&lt;/td&gt;
&lt;td&gt;45-90 min&lt;/td&gt;
&lt;td&gt;&amp;lt; 2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission errors per clone&lt;/td&gt;
&lt;td&gt;8-12&lt;/td&gt;
&lt;td&gt;0-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security audit failures&lt;/td&gt;
&lt;td&gt;4/mo&lt;/td&gt;
&lt;td&gt;0/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer self-service&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent clone setup&lt;/td&gt;
&lt;td&gt;1 at a time&lt;/td&gt;
&lt;td&gt;10+ parallel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We've solved both major challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Database reference repointing (Part 2)&lt;/li&gt;
&lt;li&gt;✅ Permissions and RBAC (Part 3)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But our solution still processes schemas &lt;strong&gt;sequentially&lt;/strong&gt;. In Part 4, we'll make it production-ready with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallel processing&lt;/strong&gt; with ASYNC/AWAIT (73% faster)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume-from-failure&lt;/strong&gt; capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit logging&lt;/strong&gt; and observability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task suspension&lt;/strong&gt; for cost control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-grade&lt;/strong&gt; orchestration&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Next: Part 4: Parallelization and Production Features →&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60"&gt;Part 2: Repointing Database References&lt;/a&gt;  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About This Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is Part 3 of a 4-part series on production-grade Snowflake database cloning. All code is available in the &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; with complete documentation and examples.&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>cloning</category>
      <category>datawarehouse</category>
      <category>lakehouse</category>
    </item>
    <item>
      <title>Part 2: Snowflake Clone++: Repointing Database References and Recreating Streams</title>
      <dc:creator>Krishna Tangudu</dc:creator>
      <pubDate>Fri, 24 Apr 2026 15:40:39 +0000</pubDate>
      <link>https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60</link>
      <guid>https://dev.to/swaroop_krishna_e2f4b83b2/part-2-snowflake-clone-repointing-database-references-and-recreating-streams-3f60</guid>
      <description>&lt;p&gt;&lt;strong&gt;Previously:&lt;/strong&gt; In &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk"&gt;Part 1&lt;/a&gt;, we saw how zero-copy cloning is revolutionary but leaves us with broken references everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this post:&lt;/strong&gt; Learn how to find and fix hardcoded database references in views, stored procedures, functions, tasks, and Iceberg tables — plus how to recreate streams that broke during cloning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Reference Problem
&lt;/h2&gt;

&lt;p&gt;Your cloned database has perfect permissions, but nothing works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Try to query a view in the clone&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_metrics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Error: Object 'PRODUCTION_DB.SILVER.CUSTOMERS' does not exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The view definition is still pointing to production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;GET_DDL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'VIEW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dev_project_db.analytics.customer_metrics'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_metrics&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;order_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;  &lt;span class="c1"&gt;-- ⚠️ Wrong database!&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The code was copied exactly as-is.&lt;/strong&gt; Every database reference needs updating.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge: Finding All References
&lt;/h2&gt;

&lt;p&gt;Database references hide everywhere:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Views (The Easy Ones)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view_definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;view_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%production_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Result: 186 views need fixing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Stored Procedures (The Tricky Ones)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;procedure_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;procedure_definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;procedures&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;procedure_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%production_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Problem: JavaScript, SQL, Python, Scala, Java...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Functions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function_definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;functions&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;function_definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%production_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Tasks (The Sneaky Ones)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%production_db%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Tasks can call procedures that call views...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Streams (The Broken Ones)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;STREAMS&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;dev_project_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Result: Every stream shows STALE = TRUE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Strategy: GET_DDL + String Replacement
&lt;/h2&gt;

&lt;p&gt;Our battle plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identify&lt;/strong&gt; objects with stale references (INFORMATION_SCHEMA)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract&lt;/strong&gt; full DDL using &lt;code&gt;GET_DDL()&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace&lt;/strong&gt; all occurrences of source database with clone database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt; updated DDL to recreate the object&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simple concept, but devils in the details.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repointing Views
&lt;/h2&gt;

&lt;p&gt;Views are straightforward because their definitions are directly accessible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Conceptual flow&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;viewRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT TABLE_NAME, VIEW_DEFINITION &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.VIEWS &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE TABLE_SCHEMA = '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;' &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AND VIEW_DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;viewRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;viewDef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace database references (case-insensitive)&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewDef&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Execute updated DDL&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;views_fixed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;VIEW_DEFINITION&lt;/code&gt; contains full CREATE statement&lt;/li&gt;
&lt;li&gt;Simple string replacement updates all references&lt;/li&gt;
&lt;li&gt;Re-executing DDL replaces the view atomically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L75-L130&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Repointing Procedures (The Hard Part)
&lt;/h2&gt;

&lt;p&gt;Procedures are tricky because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;INFORMATION_SCHEMA truncates long definitions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Must use &lt;strong&gt;GET_DDL()&lt;/strong&gt; for full text&lt;/li&gt;
&lt;li&gt;Must handle &lt;strong&gt;parameter signatures&lt;/strong&gt; correctly&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Parameter Signature Problem
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- INFORMATION_SCHEMA shows:&lt;/span&gt;
&lt;span class="n"&gt;PROCEDURE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calculate_metrics&lt;/span&gt;
&lt;span class="n"&gt;ARGUMENT_SIGNATURE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- But GET_DDL requires type-only signature:&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;GET_DDL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PROCEDURE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'schema.calculate_metrics(DATE, DATE, NUMBER)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;--                                                      ^^^^ Types only!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We must parse signatures to extract just the types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parsing Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Convert "(start_date DATE, end_date DATE)" &lt;/span&gt;
&lt;span class="c1"&gt;// To: "(DATE, DATE)"&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stripParamNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;()&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;()&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;()&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// "start_date DATE" → ["start_date", "DATE"]&lt;/span&gt;
        &lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Take type part&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Repointing Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get procedures that reference source database&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;procRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT PROCEDURE_NAME, ARGUMENT_SIGNATURE &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.PROCEDURES &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE PROCEDURE_DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;procRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;procName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;procRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;procSig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;procRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;typeSig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stripParamNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;procSig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Key step!&lt;/span&gt;

    &lt;span class="c1"&gt;// Get full DDL with correct signature&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT GET_DDL('PROCEDURE', '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="nx"&gt;cloneDb&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;procName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;typeSig&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace database references&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Recreate procedure&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDDL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;procedures_fixed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L132-L195&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Repointing Functions
&lt;/h2&gt;

&lt;p&gt;Functions work exactly like procedures (same signature challenge):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Same pattern as procedures&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Find functions with stale references&lt;/span&gt;
&lt;span class="c1"&gt;// 2. Strip parameter names from signatures&lt;/span&gt;
&lt;span class="c1"&gt;// 3. Get full DDL using GET_DDL('FUNCTION', ...)&lt;/span&gt;
&lt;span class="c1"&gt;// 4. Replace database references&lt;/span&gt;
&lt;span class="c1"&gt;// 5. Execute updated DDL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L197-L250&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Repointing Tasks
&lt;/h2&gt;

&lt;p&gt;Tasks have an additional requirement: &lt;strong&gt;SUSPEND first&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get tasks that reference source database&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;taskRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT NAME FROM clone_db.INFORMATION_SCHEMA.TASKS &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;taskName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taskRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// IMPORTANT: Suspend before modifying&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALTER TASK &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;taskName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; SUSPEND&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Get DDL, replace references, recreate&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;taskDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT GET_DDL('TASK', '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;taskName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newTaskDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taskDDL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTaskDDL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;tasks_fixed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Tasks remain SUSPENDED (intentional for non-prod)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Point:&lt;/strong&gt; Tasks remain SUSPENDED after repointing. Perfect for dev/test environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L252-L295&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Recreating Streams (The Special Case)
&lt;/h2&gt;

&lt;p&gt;Streams can't be "repointed" — they must be &lt;strong&gt;dropped and recreated&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Streams Are Different
&lt;/h3&gt;

&lt;p&gt;When you clone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production Stream                 Cloned Stream (BROKEN)
  ├─ Tracks: prod_db.data.orders    ├─ Still tracks: prod_db.data.orders ⚠️
  ├─ Offset: Transaction 12,456     ├─ Offset: LOST ⚠️
  └─ Status: Current                └─ Status: STALE ⚠️
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stream object clones but:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Still tracks the &lt;strong&gt;source table in production&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Loses its &lt;strong&gt;offset position&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Becomes immediately &lt;strong&gt;STALE&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Recreation Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get all streams in schema&lt;/span&gt;
&lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHOW STREAMS IN SCHEMA &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cloneDb&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;streamRS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT "name", "base_tables", "stale" &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;streamName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;streamRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Get stream DDL&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT GET_DDL('STREAM', '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="nx"&gt;cloneDb&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;streamName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace database references&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Drop and recreate&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DROP STREAM IF EXISTS &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;streamName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDDL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;streams_recreated&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/03_clone_streams.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/03_clone_streams.sql#L65-L140&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Important: Stream Offset Behavior
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Original stream (in production)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;STREAM&lt;/span&gt; &lt;span class="n"&gt;prod_stream&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;production_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Has been tracking changes for weeks, offset at transaction 12345&lt;/span&gt;

&lt;span class="c1"&gt;-- After recreating in clone&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;STREAM&lt;/span&gt; &lt;span class="n"&gt;dev_stream&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dev_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Offset starts at CURRENT_TIMESTAMP (no historical changes)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implication:&lt;/strong&gt; Cloned streams don't have historical change data. They start fresh.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validation: Ensuring Everything Worked
&lt;/h2&gt;

&lt;p&gt;After repointing, verify the clone is healthy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Check for views still referencing production&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;staleViews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT TABLE_SCHEMA, TABLE_NAME &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.VIEWS &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE VIEW_DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Check for procedures still referencing production&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;staleProcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT PROCEDURE_SCHEMA, PROCEDURE_NAME &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.PROCEDURES &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE PROCEDURE_DEFINITION ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Check for stale streams&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;staleStreams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHOW STREAMS WHERE stale = 'true' &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OR base_tables ILIKE '%source_db%'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Generate report&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;staleViews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;staleProcs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;staleStreams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PASS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WARN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stale_views&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;staleViews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stale_procedures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;staleProcs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stale_streams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;staleStreams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full implementation:&lt;/strong&gt; &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning/blob/main/sql/02_clone_repoint.sql" rel="noopener noreferrer"&gt;&lt;code&gt;sql/02_clone_repoint.sql#L297-L365&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;sp_validate_clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DEV_PROJECT_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTION_DB'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Result:&lt;/span&gt;
&lt;span class="c1"&gt;-- {&lt;/span&gt;
&lt;span class="c1"&gt;--   "status": "PASS",&lt;/span&gt;
&lt;span class="c1"&gt;--   "stale_views": 0,&lt;/span&gt;
&lt;span class="c1"&gt;--   "stale_procedures": 0,&lt;/span&gt;
&lt;span class="c1"&gt;--   "stale_streams": 0&lt;/span&gt;
&lt;span class="c1"&gt;-- }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance: Sequential vs Parallel
&lt;/h2&gt;

&lt;p&gt;For large databases with many schemas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Time for 6 schemas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sequential (one at a time)&lt;/td&gt;
&lt;td&gt;~45 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parallel (ASYNC/AWAIT)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~8 minutes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We'll cover parallelization in detail in Part 4.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Case Sensitivity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Won't catch lowercase references&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Handle both cases&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newDDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ddl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloneDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Partial Matches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// If source_db = "PROD"&lt;/span&gt;
&lt;span class="c1"&gt;// This accidentally replaces "PRODUCTION_TABLE" → "DEV_DUCTION_TABLE"&lt;/span&gt;

&lt;span class="c1"&gt;// Solution: Use specific database names or word boundaries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Hardcoded Strings in Procedures
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This won't be caught by simple string replacement&lt;/span&gt;
&lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="nx"&gt;PROCEDURE&lt;/span&gt; &lt;span class="nf"&gt;my_proc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;AS&lt;/span&gt;
&lt;span class="nx"&gt;$$&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PRODUCTION_DB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Hardcoded!&lt;/span&gt;
    &lt;span class="nx"&gt;snowflake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;sqlText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.schema.table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best practice:&lt;/strong&gt; Use parameters or configuration tables, not hardcoded strings.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling Iceberg Tables
&lt;/h2&gt;

&lt;p&gt;Iceberg tables introduce additional complexity when repointing because they reference external volumes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenge
&lt;/h3&gt;

&lt;p&gt;After cloning, Iceberg tables still point to production external volumes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check Iceberg table after cloning&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;ICEBERG&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;dev_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- EXTERNAL_VOLUME: prod_iceberg_volume ⚠️&lt;/span&gt;

&lt;span class="c1"&gt;-- Try to query&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dev_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events_iceberg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Error: Database DEV_DB does not have READ access to EXTERNAL VOLUME 'prod_iceberg_volume'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution: Grant Volume Access
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// During repoint, identify and grant Iceberg volume access&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;icebergTables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT DISTINCT external_volume &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.TABLES &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE table_type IN ('ICEBERG TABLE', 'DYNAMIC ICEBERG TABLE') &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AND external_volume IS NOT NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;icebergTables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRANT READ ON EXTERNAL VOLUME &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; TO DATABASE &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;clone_db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security consideration:&lt;/strong&gt; Dev environment now has read access to production Iceberg storage. This is usually acceptable for clones since:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They share the same data anyway (zero-copy)&lt;/li&gt;
&lt;li&gt;Cost tracking separates dev/prod compute&lt;/li&gt;
&lt;li&gt;Any writes are isolated to a dedicated storage location and do not interfere with production data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dynamic Iceberg Tables
&lt;/h3&gt;

&lt;p&gt;Dynamic Iceberg tables lose their "dynamic" status after cloning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Flag dynamic Iceberg tables for manual review&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;dynamicIceberg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT table_schema, table_name &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FROM clone_db.INFORMATION_SCHEMA.TABLES &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE table_type = 'DYNAMIC ICEBERG TABLE'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Log warning: These tables exist but won't refresh automatically&lt;/span&gt;
&lt;span class="c1"&gt;// Must be recreated or converted to static if needed in clone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.snowflake.com/en/user-guide/tables-iceberg-manage" rel="noopener noreferrer"&gt;check known limitations here: &lt;br&gt;
&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Metrics
&lt;/h2&gt;

&lt;p&gt;After implementing automated repointing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before (Manual)&lt;/th&gt;
&lt;th&gt;After (Automated)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to repoint 6 schemas&lt;/td&gt;
&lt;td&gt;8-12 hours&lt;/td&gt;
&lt;td&gt;8 minutes (parallel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missed references&lt;/td&gt;
&lt;td&gt;10-15 per clone&lt;/td&gt;
&lt;td&gt;0-1 per clone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed views&lt;/td&gt;
&lt;td&gt;5-8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human errors&lt;/td&gt;
&lt;td&gt;Several per clone&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Success rate&lt;/td&gt;
&lt;td&gt;80-85%&lt;/td&gt;
&lt;td&gt;99%+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We've now solved the most visible problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Database reference repointing&lt;/li&gt;
&lt;li&gt;✅ Stream recreation&lt;/li&gt;
&lt;li&gt;✅ Iceberg table handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But even with all references fixed, you &lt;strong&gt;still can't access anything&lt;/strong&gt; without proper permissions! In Part 3, we'll tackle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permission management&lt;/strong&gt; - Dynamic role creation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RBAC automation&lt;/strong&gt; - Configuration-driven grants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership transfers&lt;/strong&gt; - Breaking free from production roles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then in Part 4:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallel processing&lt;/strong&gt; with ASYNC/AWAIT (73% faster)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume-from-failure&lt;/strong&gt; capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-grade&lt;/strong&gt; orchestration&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Next:&lt;/strong&gt; Part 3: Solving Permissions and RBAC &lt;br&gt;
&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/swaroop_krishna_e2f4b83b2/part-1-understanding-snowflake-cloning-and-why-we-need-clone-4flk"&gt;Part 1: The Problem and the Promise&lt;/a&gt;  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About This Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is Part 2 of a 4-part series on production-grade Snowflake database cloning. All code is available in the &lt;a href="https://github.com/LALITHASWAROOPK/snowflake_cloning" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; with complete documentation and examples.&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>clone</category>
      <category>zerocopy</category>
      <category>clouddatawarehouse</category>
    </item>
  </channel>
</rss>
