<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>QueryFlux Blog</title>
        <link>https://queryflux.dev/blog</link>
        <description>QueryFlux Blog</description>
        <lastBuildDate>Wed, 21 Oct 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Offload Snowflake queries without changing clients]]></title>
            <link>https://queryflux.dev/blog/offload-snowflake-queries</link>
            <guid>https://queryflux.dev/blog/offload-snowflake-queries</guid>
            <pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Point Snowflake clients at QueryFlux, keep heavy work on Snowflake, and offload the rest to Trino or StarRocks — no dashboard or notebook rewrites.]]></description>
            <content:encoded><![CDATA[<p>Snowflake bills for warehouse time. A lot of that time goes to queries that don't need it: dashboard refreshes, small lookups, exploratory <code>SELECT</code>s from notebooks. If your data already lives in open formats like Iceberg, an engine you already run (Trino, StarRocks, DuckDB) could answer many of those queries.</p>
<p>The blocker is rarely the engine. It's the clients. Dashboards, notebooks, dbt profiles and application code all speak Snowflake's protocol and Snowflake's SQL. Moving a workload means touching every one of them.</p>
<p>QueryFlux removes that step. Snowflake clients connect to it as if it were Snowflake, and it decides which engine runs each session, Snowflake included.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How it works<a href="https://queryflux.dev/blog/offload-snowflake-queries#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h2>
<figure class="blog-figure"><img src="https://queryflux.dev/img/blog/snowflake-offload.svg" alt="Snowflake clients connect to QueryFlux; BI and exploration go to Trino or StarRocks on Iceberg, everything else stays on Snowflake via ADBC"><figcaption>Same Snowflake login UX. Session routing picks Trino/StarRocks or Snowflake.</figcaption></figure>
<p>QueryFlux speaks the Snowflake HTTP protocol and the SQL API v2. The Python connector, JDBC drivers and SnowSQL log in to QueryFlux the same way they log in to Snowflake. At login, a router picks a cluster group for the session: by the warehouse, role or user the client asked for. Every query in that session runs on that group, with Snowflake SQL translated to the target engine's dialect by sqlglot.</p>
<p>Snowflake itself stays one of the backends, through the ADBC Snowflake driver, so anything you don't move keeps running exactly where it ran before.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-config">The config<a href="https://queryflux.dev/blog/offload-snowflake-queries#the-config" class="hash-link" aria-label="Direct link to The config" title="Direct link to The config" translate="no">​</a></h2>
<p>Turn on the Snowflake frontend, declare both backends, and route by warehouse:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockTitle_OeMC">config.yaml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token key atrule">queryflux</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">frontends</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">snowflakeHttp</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">enabled</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token boolean important" style="color:rgb(255, 88, 116)">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">8443</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">clusters</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">snowflake</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">engine</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> adbc</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">driver</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> snowflake</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">uri</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> svc_user@myaccount/analytics/public</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">auth</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> keyPair</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">username</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> SVC_ACCOUNT</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">privateKeyPem</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(173, 219, 103)">"..."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">trino-1</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">engine</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> trino</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">endpoint</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> https</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain">//trino.internal</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token number" style="color:rgb(247, 140, 108)">8443</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">clusterGroups</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">snowflake</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token key atrule">maxRunningQueries</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">50</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token key atrule">members</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain">snowflake</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">lakehouse</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token key atrule">maxRunningQueries</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">100</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token key atrule">members</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain">trino</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token number" style="color:rgb(247, 140, 108)">1</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">routers</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> pythonScript</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">script</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(173, 219, 103)"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">      def route(query: str, ctx: dict) -&gt; str | None:</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">          # Sessions that ask for the BI warehouse run on the lakehouse engine.</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">          if ctx.get("extra", {}).get("snowflake.warehouse") == "BI_WH":</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">              return "lakehouse"</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">          return None</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">routingFallback</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> snowflake</span><br></div></code></pre></div></div>
<p>The router sees the login's warehouse, role and schema (<code>snowflake.warehouse</code>, <code>snowflake.role</code>, <code>snowflake.schema</code>) and the user. Here, sessions on <code>BI_WH</code> go to Trino; every other session stays on Snowflake. Start with one warehouse, watch it in Studio, then move the next.</p>
<p>ADBC drivers aren't bundled in the image; declare the Snowflake driver when you start the container (see <a class="" href="https://queryflux.dev/docs/operations/adbc-drivers">ADBC drivers</a>).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="point-the-clients-at-queryflux">Point the clients at QueryFlux<a href="https://queryflux.dev/blog/offload-snowflake-queries#point-the-clients-at-queryflux" class="hash-link" aria-label="Direct link to Point the clients at QueryFlux" title="Direct link to Point the clients at QueryFlux" translate="no">​</a></h2>
<p>The only change on the client side is the host:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token keyword" style="color:rgb(127, 219, 202)">import</span><span class="token plain"> snowflake</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">connector</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">conn </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> snowflake</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">connector</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">connect</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    account</span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token string" style="color:rgb(173, 219, 103)">"queryflux"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    user</span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token string" style="color:rgb(173, 219, 103)">"analyst"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    password</span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token string" style="color:rgb(173, 219, 103)">"..."</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    host</span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token string" style="color:rgb(173, 219, 103)">"queryflux.internal"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    port</span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token number" style="color:rgb(247, 140, 108)">8443</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    warehouse</span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token string" style="color:rgb(173, 219, 103)">"BI_WH"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain">      </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># this session will run on Trino</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    database</span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token string" style="color:rgb(173, 219, 103)">"analytics"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    schema</span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token string" style="color:rgb(173, 219, 103)">"public"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><br></div></code></pre></div></div>
<p>Dashboards keep their SQL. Notebooks keep their code. Only the host in the connection settings changes, once.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-has-to-be-true">What has to be true<a href="https://queryflux.dev/blog/offload-snowflake-queries#what-has-to-be-true" class="hash-link" aria-label="Direct link to What has to be true" title="Direct link to What has to be true" translate="no">​</a></h2>
<p>Offloading is only as good as the engine receiving the work, so check these before you move a workload:</p>
<ul>
<li class=""><strong>The data is readable by the target engine.</strong> The usual setup: tables in Iceberg that Snowflake and Trino (or StarRocks) both read. QueryFlux maps Snowflake's <code>database.schema.table</code> to the engine's <code>catalog.schema.table</code>, so name the engine's catalog after the Snowflake database.</li>
<li class=""><strong>The SQL translates.</strong> sqlglot covers most Snowflake syntax, but not all of it. Run the workload's real queries through QueryFlux before switching, and add Python fixup scripts for anything that needs a nudge. Query history keeps both the original and the translated SQL, so you can see exactly what ran.</li>
<li class=""><strong>Routing is per session.</strong> The group is chosen at login and kept for the session; choose by warehouse, role or user, not per query.</li>
<li class=""><strong>Some Snowflake features aren't proxied.</strong> Async execution, multiple statements per request, <code>ALTER SESSION</code> and server-side cancel aren't supported, and TLS is terminated in front of QueryFlux. The <a class="" href="https://queryflux.dev/docs/architecture/frontends/snowflake">Snowflake frontend docs</a> list the details.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-you-get">What you get<a href="https://queryflux.dev/blog/offload-snowflake-queries#what-you-get" class="hash-link" aria-label="Direct link to What you get" title="Direct link to What you get" translate="no">​</a></h2>
<ul>
<li class=""><strong>A way out of all-or-nothing.</strong> Move one warehouse's workload at a time, and move it back by changing one rule.</li>
<li class=""><strong>The same clients.</strong> No dashboard rewrites, no new drivers.</li>
<li class=""><strong>One record.</strong> Every query, on Snowflake or elsewhere, lands in one history with its user, route and timing, so you can compare before and after.</li>
<li class=""><strong>Your engines, your environment.</strong> QueryFlux is open source and runs next to the engines you already operate.</li>
</ul>
<p><strong>Try it:</strong> the <a class="" href="https://queryflux.dev/docs/architecture/frontends/snowflake">Snowflake frontend docs</a> cover configuration and clients; <a class="" href="https://queryflux.dev/docs/architecture/routing-and-clusters">routing and clusters</a> covers the Python router.</p>]]></content:encoded>
            <category>Guides</category>
        </item>
        <item>
            <title><![CDATA[One endpoint for Iceberg on Trino and StarRocks]]></title>
            <link>https://queryflux.dev/blog/iceberg-trino-starrocks-one-endpoint</link>
            <guid>https://queryflux.dev/blog/iceberg-trino-starrocks-one-endpoint</guid>
            <pubDate>Fri, 16 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Run Trino and StarRocks on the same Iceberg tables behind one QueryFlux SQL endpoint — route BI and notebooks without client rewrites.]]></description>
            <content:encoded><![CDATA[<p>Apache Iceberg solved the hard part: one copy of your data that many engines can read. Trino reads it for interactive and federated SQL. StarRocks reads the same tables for fast, high-concurrency dashboards. On paper you have the best of both.</p>
<p>In practice, every client still has to choose. Your BI tool talks MySQL to StarRocks; your notebooks talk to Trino over HTTP; each has its own connection string, its own dialect and its own idea of capacity. Moving a dashboard from one engine to the other means touching the dashboard.</p>
<p>This guide puts one SQL endpoint in front of both engines, so clients connect once and QueryFlux sends each query to the engine that should run it. You can run the whole stack on a laptop.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-youll-build">What you'll build<a href="https://queryflux.dev/blog/iceberg-trino-starrocks-one-endpoint#what-youll-build" class="hash-link" aria-label="Direct link to What you'll build" title="Direct link to What you'll build" translate="no">​</a></h2>
<figure class="blog-figure"><img src="https://queryflux.dev/img/blog/iceberg-stack.svg" alt="Architecture: BI over MySQL and notebooks over Trino HTTP into QueryFlux, routing to Trino and StarRocks on a shared Iceberg catalog and object storage"><figcaption>BI and notebooks share one front door; both engines read the same Iceberg tables.</figcaption></figure>
<ul>
<li class="">An Iceberg REST catalog (Lakekeeper) on S3-compatible storage (RustFS), loaded with TPC-H.</li>
<li class="">Trino and StarRocks, both reading the same Iceberg tables.</li>
<li class="">QueryFlux in front: Trino HTTP on port 8080, MySQL wire on port 3306, and Studio on port 3000 to see where every query went.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-start-the-stack">1. Start the stack<a href="https://queryflux.dev/blog/iceberg-trino-starrocks-one-endpoint#1-start-the-stack" class="hash-link" aria-label="Direct link to 1. Start the stack" title="Direct link to 1. Start the stack" translate="no">​</a></h2>
<p>You need Docker and Docker Compose, with enough memory for Trino and StarRocks.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token function" style="color:rgb(130, 170, 255)">git</span><span class="token plain"> clone https://github.com/lakeops-org/queryflux.git</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(255, 203, 139)">cd</span><span class="token plain"> queryflux/examples/full-stack</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token function" style="color:rgb(130, 170, 255)">docker</span><span class="token plain"> compose up </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">-d</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">--wait</span><br></div></code></pre></div></div>
<p>Load TPC-H into Iceberg through Trino, then register the same catalog in StarRocks:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token function" style="color:rgb(130, 170, 255)">docker</span><span class="token plain"> compose </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">--profile</span><span class="token plain"> loader run </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">--rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">-T</span><span class="token plain"> data-loader</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token function" style="color:rgb(130, 170, 255)">docker</span><span class="token plain"> compose </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">--profile</span><span class="token plain"> loader run </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">--rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">-T</span><span class="token plain"> starrocks-catalog-setup</span><br></div></code></pre></div></div>
<p>Both engines now see the same eight TPC-H tables under <code>lakekeeper.tpch</code>. For more data, rerun the loader with <code>TPCH_SCALE=sf1</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-same-table-two-engines-one-endpoint">2. Same table, two engines, one endpoint<a href="https://queryflux.dev/blog/iceberg-trino-starrocks-one-endpoint#2-same-table-two-engines-one-endpoint" class="hash-link" aria-label="Direct link to 2. Same table, two engines, one endpoint" title="Direct link to 2. Same table, two engines, one endpoint" translate="no">​</a></h2>
<p>Connect a Trino client to QueryFlux (not to Trino directly):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token function" style="color:rgb(130, 170, 255)">docker</span><span class="token plain"> compose </span><span class="token builtin class-name" style="color:rgb(255, 203, 139)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">-it</span><span class="token plain"> trino trino </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">--server</span><span class="token plain"> http://queryflux:8080 </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">--user</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(255, 203, 139)">test</span><br></div></code></pre></div></div>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token keyword" style="color:rgb(127, 219, 202)">SELECT</span><span class="token plain"> o_orderpriority </span><span class="token keyword" style="color:rgb(127, 219, 202)">AS</span><span class="token plain"> priority</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">       </span><span class="token function" style="color:rgb(130, 170, 255)">count</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token operator" style="color:rgb(127, 219, 202)">*</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">AS</span><span class="token plain"> orders</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">       </span><span class="token function" style="color:rgb(130, 170, 255)">round</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token function" style="color:rgb(130, 170, 255)">sum</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">o_totalprice</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">2</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">AS</span><span class="token plain"> revenue</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token keyword" style="color:rgb(127, 219, 202)">FROM</span><span class="token plain"> lakekeeper</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">tpch</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">orders</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token keyword" style="color:rgb(127, 219, 202)">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">BY</span><span class="token plain"> o_orderpriority</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token keyword" style="color:rgb(127, 219, 202)">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">BY</span><span class="token plain"> orders </span><span class="token keyword" style="color:rgb(127, 219, 202)">DESC</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></div></code></pre></div></div>
<p>Now connect a MySQL client to QueryFlux on port 3306 and run the same query:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token function" style="color:rgb(130, 170, 255)">docker</span><span class="token plain"> compose </span><span class="token builtin class-name" style="color:rgb(255, 203, 139)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">-it</span><span class="token plain"> starrocks mysql </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">-h</span><span class="token plain"> queryflux </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">-P</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">3306</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(214, 222, 235)">-u</span><span class="token plain"> root</span><br></div></code></pre></div></div>
<p>Same numbers, but StarRocks ran it this time. Open Studio at <a href="http://localhost:3000/" target="_blank" rel="noopener noreferrer" class="">http://localhost:3000</a> (admin / admin), go to <strong>Queries</strong>, and you'll see each query with the engine that ran it and the route it took.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-how-the-routing-works">3. How the routing works<a href="https://queryflux.dev/blog/iceberg-trino-starrocks-one-endpoint#3-how-the-routing-works" class="hash-link" aria-label="Direct link to 3. How the routing works" title="Direct link to 3. How the routing works" translate="no">​</a></h2>
<p>The example's config is short:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token key atrule">clusters</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">trino-1</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">engine</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> trino</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">endpoint</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> http</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain">//trino</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token number" style="color:rgb(247, 140, 108)">8080</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">starrocks-1</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">engine</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> starRocks</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">endpoint</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> mysql</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain">//starrocks</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token number" style="color:rgb(247, 140, 108)">9030</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">auth</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> basic</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token key atrule">username</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> root</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token key atrule">password</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(173, 219, 103)">""</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">clusterGroups</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">trino-default</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain">     </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token key atrule">maxRunningQueries</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">100</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token key atrule">members</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain">trino</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token number" style="color:rgb(247, 140, 108)">1</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">starrocks-default</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token key atrule">maxRunningQueries</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">50</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain">  </span><span class="token key atrule">members</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain">starrocks</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token number" style="color:rgb(247, 140, 108)">1</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">routers</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> protocolBased</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">trinoHttp</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> trino</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">default</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">mysqlWire</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> starrocks</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">default</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">routingFallback</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> trino</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">default</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>Clusters</strong> are the engines. QueryFlux talks to Trino over HTTP and to StarRocks over the MySQL protocol.</li>
<li class=""><strong>Cluster groups</strong> wrap them, each with its own concurrency limit. When a group is full, queries wait at the proxy instead of piling onto the engine.</li>
<li class=""><strong>Routers</strong> pick the group. Here it's simply the protocol the client used.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-route-by-workload-not-by-protocol">4. Route by workload, not by protocol<a href="https://queryflux.dev/blog/iceberg-trino-starrocks-one-endpoint#4-route-by-workload-not-by-protocol" class="hash-link" aria-label="Direct link to 4. Route by workload, not by protocol" title="Direct link to 4. Route by workload, not by protocol" translate="no">​</a></h2>
<p>Routing by protocol is a start. The real win is routing by <em>what the query is</em>, so a client keeps one connection and still lands on the right engine.</p>
<p>Send queries tagged as dashboards to StarRocks, and keep everything else on Trino. A Trino client tags its queries with the standard client-tags header, for example <code>trino --client-tags dashboard</code> or <code>X-Trino-Client-Tags: dashboard</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token key atrule">routers</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> tags</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">rules</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">tags</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token key atrule">dashboard</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token null important">null</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain">   </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># key-only tag, as Trino clients send them</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">targetGroup</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> starrocks</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">default</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> protocolBased</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">mysqlWire</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> starrocks</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">default</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">routingFallback</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> trino</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">default</span><br></div></code></pre></div></div>
<p>Or decide in Python, with the SQL, protocol, user and session in hand:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token key atrule">routers</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> pythonScript</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">script</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(173, 219, 103)"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">      def route(query: str, ctx: dict) -&gt; str | None:</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">          q = query.upper()</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">          # Short aggregations over the serving tables: StarRocks.</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">          if "GROUP BY" in q and "JOIN" not in q:</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">              return "starrocks-default"</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token scalar string" style="color:rgb(173, 219, 103)">          return None  # fall through to the next router</span><br></div></code></pre></div></div>
<p>A Trino client routed to StarRocks still writes Trino SQL: QueryFlux translates the dialect with sqlglot before the query reaches StarRocks.</p>
<p>You can also put both engines in <strong>one</strong> group and move traffic gradually, for example 10% to StarRocks, then 50%, then all of it, with a <code>weighted</code> strategy, while Studio shows per-engine latency side by side. That's how you migrate a workload between engines without touching a single client.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-matters">Why this matters<a href="https://queryflux.dev/blog/iceberg-trino-starrocks-one-endpoint#why-this-matters" class="hash-link" aria-label="Direct link to Why this matters" title="Direct link to Why this matters" translate="no">​</a></h2>
<p>Iceberg made the data portable. A single SQL endpoint in front of your engines makes the <em>queries</em> portable:</p>
<ul>
<li class="">Clients connect once, over the protocol they already speak.</li>
<li class="">Each workload runs on the engine that fits it, and you can change your mind without a migration project.</li>
<li class="">Every query, on every engine, lands in one history with its route, timing and user.</li>
</ul>
<p>The same setup takes more engines (DuckDB, ClickHouse, Athena, and Snowflake, BigQuery or Databricks through ADBC) as you add them.</p>
<p><strong>Try it:</strong> the full walkthrough is in the <a class="" href="https://queryflux.dev/docs/quickstart/multi-engine">multi-engine quickstart</a>. Routing options are in <a class="" href="https://queryflux.dev/docs/architecture/routing-and-clusters">routing and clusters</a>.</p>]]></content:encoded>
            <category>Guides</category>
        </item>
        <item>
            <title><![CDATA[Give agents SQL, not the keys to every engine]]></title>
            <link>https://queryflux.dev/blog/agents-sql-not-keys</link>
            <guid>https://queryflux.dev/blog/agents-sql-not-keys</guid>
            <pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Put AI agents behind QueryFlux — one MCP or SQL endpoint with routing, guardrails, impersonation, and a record of every query.]]></description>
            <content:encoded><![CDATA[<p>The fastest way to give an AI agent data is also the worst one: hand it a warehouse connection string and let it write SQL. It works in a demo. In production it means one shared credential for every agent, no record of which agent ran what, and nothing between a generated <code>SELECT *</code> and your largest table.</p>
<blockquote>
<p><strong>Agents shouldn't choose engines, and they shouldn't hold warehouse passwords.</strong> Something in the query path should decide where each query runs, what it may do, and keep the record.</p>
</blockquote>
<p>This post shows how to set that up with QueryFlux using features that ship today.</p>
<figure class="blog-figure"><img src="https://queryflux.dev/img/blog/agent-query-path.svg" alt="Flow diagram: agent connects via MCP to QueryFlux, which applies guardrails and impersonation before Trino, with query history in Studio"><figcaption>Agents see one endpoint. Operators keep routing, limits, guardrails, identity, and audit in the proxy.</figcaption></figure>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-setup-in-one-file">The setup in one file<a href="https://queryflux.dev/blog/agents-sql-not-keys#the-setup-in-one-file" class="hash-link" aria-label="Direct link to The setup in one file" title="Direct link to The setup in one file" translate="no">​</a></h2>
<p>One QueryFlux config gives agents a verified identity, their own entry point, capacity pool, guardrails, and per-user identity on the engine:</p>
<figure class="blog-figure"><img src="https://queryflux.dev/img/blog/agents-mcp-config.svg" alt="QueryFlux YAML with OIDC auth required, Trino impersonation, analysts and agents groups with allowGroups, protocol router for MCP, and agent guardrails"><figcaption>Full example — adjust IdP URLs, endpoints, and limits for your environment. See <a href="https://queryflux.dev/docs/authentication">authentication</a>, <a href="https://queryflux.dev/docs/configuration">configuration</a>, and the <a href="https://queryflux.dev/docs/quickstart/ai-agent-mcp">MCP quickstart</a>.</figcaption></figure>
<p>The sections below map directly to that file.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-one-endpoint-any-agent">1. One endpoint, any agent<a href="https://queryflux.dev/blog/agents-sql-not-keys#1-one-endpoint-any-agent" class="hash-link" aria-label="Direct link to 1. One endpoint, any agent" title="Direct link to 1. One endpoint, any agent" translate="no">​</a></h2>
<p>Turn on OIDC (or another verified provider) with <code>auth.required: true</code> so MCP callers cannot connect as <code>anonymous</code>. Agents send a bearer token on every request:</p>
<figure class="blog-figure"><img src="https://queryflux.dev/img/blog/mcp-connect.svg" alt="Terminal command: claude mcp add with HTTP transport and Authorization Bearer header to QueryFlux MCP"><figcaption>Cursor and Claude Desktop use the same MCP URL; put the token in an environment variable, not in a committed config. See <a href="https://queryflux.dev/docs/architecture/frontends/mcp#connecting-a-client">Connecting a client</a>.</figcaption></figure>
<p>The agent gets tools to run a query, list schemas, describe a table, read the engine's plan, and check or cancel what it started.</p>
<p>Agents that prefer a SQL driver can connect over Trino, PostgreSQL, or MySQL instead — routing, guardrails, impersonation, and audit work the same because they live in the query path, not in the MCP layer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-agents-get-their-own-lane">2. Agents get their own lane<a href="https://queryflux.dev/blog/agents-sql-not-keys#2-agents-get-their-own-lane" class="hash-link" aria-label="Direct link to 2. Agents get their own lane" title="Direct link to 2. Agents get their own lane" translate="no">​</a></h2>
<p>The <code>protocolBased</code> router sends every MCP query to the <code>agents</code> group, which has its own <code>maxRunningQueries</code>. A burst of exploratory agent queries queues in its own lane instead of starving dashboards in <code>analysts</code>.</p>
<p>MCP's <code>execute_query</code> also accepts an optional <code>engine_hint</code> that can name a cluster group and skip the router. Without a group allow-list, an authenticated agent could hint <code>analysts</code> and pick up that group's capacity without its agent guardrails. The config above sets <code>authorization.allowGroups</code> on each group so only IdP group <code>analysts</code> can reach <code>analysts</code>, and only <code>agents</code> can reach <code>agents</code>.</p>
<p>With Postgres persistence, the limit holds across every QueryFlux replica and worker, and waiting queries are served first come, first served.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-guardrails-in-the-query-path">3. Guardrails in the query path<a href="https://queryflux.dev/blog/agents-sql-not-keys#3-guardrails-in-the-query-path" class="hash-link" aria-label="Direct link to 3. Guardrails in the query path" title="Direct link to 3. Guardrails in the query path" translate="no">​</a></h2>
<p>The <code>agents</code> group gets three built-in guards on top of any global ones:</p>
<table><thead><tr><th>Guard</th><th>Effect</th></tr></thead><tbody><tr><td><code>read_only</code></td><td>Blocks anything that isn't <code>SELECT</code>, <code>WITH</code>, <code>SHOW</code>, <code>DESCRIBE</code>, or <code>EXPLAIN</code></td></tr><tr><td><code>row_limit</code></td><td>Denies queries whose outer <code>LIMIT</code> exceeds 5,000 rows; warns when there is no <code>LIMIT</code></td></tr><tr><td><code>require_predicate</code></td><td>Rejects scans of fact/event tables with no <code>WHERE</code></td></tr></tbody></table>
<p>A blocked query returns an error naming the rule that failed, so the agent can rewrite and retry. When the built-ins aren't enough, add a Python guard or call your own webhook — both receive the agent's context.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Why guardrails belong in QueryFlux</div><div class="admonitionContent_BuS1"><p>A rule in an MCP wrapper only holds while the agent uses that wrapper. A rule in the <strong>data path</strong> holds for MCP, SQL drivers, and anything else that speaks to the proxy.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-the-engine-sees-the-person-not-a-shared-account">4. The engine sees the person, not a shared account<a href="https://queryflux.dev/blog/agents-sql-not-keys#4-the-engine-sees-the-person-not-a-shared-account" class="hash-link" aria-label="Direct link to 4. The engine sees the person, not a shared account" title="Direct link to 4. The engine sees the person, not a shared account" translate="no">​</a></h2>
<p>With <code>queryAuth: impersonate</code>, QueryFlux authenticates to Trino with its own service account but runs each query as the <strong>verified</strong> user from the bearer token. The engine applies that person's permissions and writes that person into its audit log. There is no shared password for an agent to leak or overreach with — and without <code>auth.required</code>, impersonation would only forward <code>anonymous</code>.</p>
<p>Trino and ClickHouse support impersonation. For Snowflake and Trino, QueryFlux can instead exchange the caller's OAuth token for one the engine accepts, or pass the caller's credential through.</p>
<p>For finer control, add row filters and column masks from OPA or Cerbos — they apply to agents exactly as they apply to people.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-every-query-has-an-owner">5. Every query has an owner<a href="https://queryflux.dev/blog/agents-sql-not-keys#5-every-query-has-an-owner" class="hash-link" aria-label="Direct link to 5. Every query has an owner" title="Direct link to 5. Every query has an owner" translate="no">​</a></h2>
<p>QueryFlux stores agent context when it is present:</p>
<ul>
<li class=""><strong>MCP:</strong> <code>agent_id</code> defaults from the authenticated user and <code>conversation_id</code> from the MCP session. <code>step_index</code> and <code>tool_call_id</code> are optional — the client must send them (as tool parameters or <code>X-Step-Index</code> / <code>X-Tool-Call-Id</code> headers) if you want a full step-by-step trail.</li>
<li class=""><strong>Trino HTTP (and other SQL drivers):</strong> both <code>X-Agent-Id</code> and <code>X-Conversation-Id</code> are required for the request to count as agentic; otherwise it is treated as a normal client query. Optional headers cover step, tool call, and intent.</li>
</ul>
<p>In Studio, the <strong>Agents</strong> and <strong>Conversations</strong> pages group history by agent and session so you can replay what was recorded: every query, where it ran, how long it took, and which guardrail stopped it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-this-adds-up-to">What this adds up to<a href="https://queryflux.dev/blog/agents-sql-not-keys#what-this-adds-up-to" class="hash-link" aria-label="Direct link to What this adds up to" title="Direct link to What this adds up to" translate="no">​</a></h2>
<p>An agent gets one endpoint and the SQL it needs. You keep control of:</p>
<ul>
<li class="">which engine runs each query;</li>
<li class="">how much of the cluster agents can use;</li>
<li class="">what agents may do;</li>
<li class="">whose permissions apply on the engine;</li>
<li class="">a record of agent and conversation — plus step/tool metadata when the client supplies it.</li>
</ul>
<p>It works the same over MCP and over a SQL driver, on the engines you already run.</p>
<div class="blog-cta"><p>Start with the <a class="" href="https://queryflux.dev/docs/quickstart/ai-agent-mcp">MCP quickstart</a>, then read <a class="" href="https://queryflux.dev/docs/architecture/guardrails">guardrails</a>, <a class="" href="https://queryflux.dev/docs/agentic/overview">agent context</a>, and <a class="" href="https://queryflux.dev/docs/authentication">authentication</a>.</p></div>]]></content:encoded>
            <category>Guides</category>
        </item>
        <item>
            <title><![CDATA[Why QueryFlux: the missing layer above query engines]]></title>
            <link>https://queryflux.dev/blog/why-queryflux</link>
            <guid>https://queryflux.dev/blog/why-queryflux</guid>
            <pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Open table formats made data interoperable. Query engines are still islands. Why QueryFlux exists as a neutral SQL layer above the engines.]]></description>
            <content:encoded><![CDATA[<p>For the last few years, the data lake world has been quietly fixing one of data infrastructure's oldest problems: lock-in.</p>
<p>Apache Iceberg and the broader open table format movement changed the contract between storage and compute. Data stopped belonging to a single engine. Instead of copying the same dataset into five systems to support five workloads, teams keep one copy and let many engines read it: Spark for batch, Trino for interactive SQL, DuckDB for local analysis, StarRocks for serving. One dataset, many readers.</p>
<blockquote>
<p><strong>Data became interoperable. Query engines did not.</strong></p>
</blockquote>
<figure class="blog-figure"><img src="https://queryflux.dev/img/blog/interoperability-layers.svg" alt="Diagram: open table formats unify storage while query engines remain separate until QueryFlux provides one SQL front door"><figcaption>Open formats fixed the storage layer. Clients still face N engines × M protocols unless something sits in between.</figcaption></figure>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="we-solved-storage-interoperability-compute-is-next">We solved storage interoperability. Compute is next.<a href="https://queryflux.dev/blog/why-queryflux#we-solved-storage-interoperability-compute-is-next" class="hash-link" aria-label="Direct link to We solved storage interoperability. Compute is next." title="Direct link to We solved storage interoperability. Compute is next." translate="no">​</a></h2>
<p>Most teams still treat query engines as silos, and they pay for it in ways that compound quietly.</p>
<p>Every engine speaks its own wire protocol, has its own SQL dialect quirks, and needs its own connection management. Multiply engines by clients (BI tools, notebooks, application code, CLIs) and you don't get a linear integration problem; you get a combinatorial one. Each pairing needs its own driver, dialect handling, and retry logic. Routing, rate limiting, and observability get reinvented in isolation, over and over.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cost-and-performance-follow-the-same-pattern">Cost and performance follow the same pattern<a href="https://queryflux.dev/blog/why-queryflux#cost-and-performance-follow-the-same-pattern" class="hash-link" aria-label="Direct link to Cost and performance follow the same pattern" title="Direct link to Cost and performance follow the same pattern" translate="no">​</a></h3>
<p>Cloud analytical systems charge either for compute time or for data scanned, and query shapes skew toward compute-bound or IO-bound work. Each class tends to run cheaper under a different pricing model.</p>
<p>While building QueryFlux we ran the same analytical SQL across engines with different pricing models. A poor match between query shape and billing model inflated cost by <strong>2–5×</strong> against a better-matched engine. In a prototype that steered CPU-heavy work to compute-priced backends and scan-heavy work to byte-priced ones, total cost for one representative suite fell by up to about <strong>56%</strong>, and some individual queries by up to about <strong>90%</strong>.</p>
<p>That was a prototype, not a built-in cost optimizer (today you express that logic as routing rules or a Python router), but the gap it showed is structural.</p>
<p>Performance tells the same story from another angle. Trino is built for federation: a coordinator plans the query, schedules stages across workers, and assembles results. For a query scanning millions of rows across dozens of nodes, that overhead is well spent. For a quick filter or a small aggregation, it isn't. DuckDB runs in-process, with no network round trips and no worker coordination, and the same small query can come back in tens of milliseconds instead of a second.</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Prototype, not a productized router</div><div class="admonitionContent_BuS1"><p>QueryFlux ships the <strong>primitives</strong> — regex routers, protocol routers, Python <code>route()</code>, cluster groups, and dialect translation. You decide the policy; the proxy enforces it in one place.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="route-by-shape-not-by-hope">Route by shape, not by hope<a href="https://queryflux.dev/blog/why-queryflux#route-by-shape-not-by-hope" class="hash-link" aria-label="Direct link to Route by shape, not by hope" title="Direct link to Route by shape, not by hope" translate="no">​</a></h3>
<p>With QueryFlux, you express “small local queries → DuckDB, everything else → Trino” as configuration instead of application code:</p>
<figure class="blog-figure"><img src="https://queryflux.dev/img/blog/routing-rules-yaml.svg" alt="YAML routing rules: regex on FROM local.* to duckdb-local cluster group, plus protocol-based Trino HTTP routing"><figcaption>Declarative routing in <code>config.yaml</code> — see <a href="https://queryflux.dev/docs/configuration">configuration</a>.</figcaption></figure>
<p>When regex isn't enough, drop to Python for the same decision:</p>
<figure class="blog-figure"><img src="https://queryflux.dev/img/blog/routing-rules-python.svg" alt="Python route function sending local non-JOIN queries to duckdb-local"><figcaption>A custom router sees the SQL, protocol, user, and session — <a href="https://queryflux.dev/docs/architecture/routing-and-clusters">routing docs</a>.</figcaption></figure>
<p>The client (BI tool, notebook, or CLI) sends queries as it always has. QueryFlux picks the engine and translates the dialect. The application doesn't change.</p>
<p>Without one layer in front of the engines, capturing those savings is hard: there is no single place to observe queries, apply routing logic, and dispatch to the right backend. Teams end up locked into one engine, or maintaining bespoke glue that can't reason about cost or capability in one place.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-queryflux-is">What QueryFlux is<a href="https://queryflux.dev/blog/why-queryflux#what-queryflux-is" class="hash-link" aria-label="Direct link to What QueryFlux is" title="Direct link to What QueryFlux is" translate="no">​</a></h2>
<p>QueryFlux is an open-source SQL proxy and router, written in Rust: <strong>one front door</strong>, many engines, explicit routing, automatic dialect translation, and shared capacity limits and observability.</p>
<p>Clients connect over the protocol they already speak:</p>
<table><thead><tr><th>Front door</th><th>Typical clients</th></tr></thead><tbody><tr><td>Trino HTTP, PostgreSQL, MySQL wire</td><td>BI, apps, ORMs</td></tr><tr><td>Snowflake HTTP, Arrow Flight SQL</td><td>Warehouses, Arrow stack</td></tr><tr><td>MCP</td><td>AI agents and assistants</td></tr></tbody></table>
<p>QueryFlux routes each query to a backend, translates SQL when needed, and streams results back in the client's native format. The frontend never knows which engine it is talking to; the engine adapter never knows which protocol the client used.</p>
<p>Native adapters cover Trino, DuckDB, StarRocks, ClickHouse, Athena, and MySQL. The Apache Arrow ADBC driver manager reaches Snowflake, BigQuery, Databricks, Redshift, PostgreSQL, SQL Server, and more. Adding an engine or client protocol means implementing one well-defined Rust trait.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-routing-works">How routing works<a href="https://queryflux.dev/blog/why-queryflux#how-routing-works" class="hash-link" aria-label="Direct link to How routing works" title="Direct link to How routing works" translate="no">​</a></h2>
<p>Routing is a two-stage process:</p>
<figure class="blog-figure"><img src="https://queryflux.dev/img/blog/routing-pipeline.svg" alt="Pipeline: query in, routers chain, cluster group, healthy cluster selection, dialect translate, engine"><figcaption>Group selection first, then cluster selection, then optional dialect translation.</figcaption></figure>
<p><strong>Group selection.</strong> A chain of routers inspects each query in order and returns a cluster group — a named pool of backends. The first match wins; if nothing matches, a fallback group takes the query. Routers can match on client protocol, HTTP headers, SQL patterns, client tags, the user's group, or a Python <code>route(query, ctx)</code> function.</p>
<p><strong>Cluster selection.</strong> Within the group, a strategy picks one backend: round-robin, least-loaded, failover in priority order, engine affinity, or weighted. A cluster must be healthy, enabled, and under its concurrency limit. If none is, the query waits in line rather than being dropped or sent to an overloaded backend.</p>
<p>Operators express intent at two levels: which pool of engines fits a class of work, and how traffic spreads across that pool.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dialect-translation-as-a-first-class-concern">Dialect translation as a first-class concern<a href="https://queryflux.dev/blog/why-queryflux#dialect-translation-as-a-first-class-concern" class="hash-link" aria-label="Direct link to Dialect translation as a first-class concern" title="Direct link to Dialect translation as a first-class concern" translate="no">​</a></h2>
<p>Routing to the right engine solves only half the problem. Engines speak slightly different SQL, and expecting clients to know the dialect of whichever engine they were routed to defeats the point.</p>
<p>QueryFlux translates with sqlglot (30+ dialects). Translation runs after routing has picked a cluster and before the SQL reaches it: the source dialect comes from the client protocol, the target from the engine. When they match, translation is skipped. Where sqlglot doesn't cover a case, Python fixup scripts can adjust the parsed query, globally or per group.</p>
<p>A client writing Trino SQL can hit DuckDB or StarRocks without knowing it. The engine becomes an infrastructure decision, not an application one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="built-to-operate-not-just-to-route">Built to operate, not just to route<a href="https://queryflux.dev/blog/why-queryflux#built-to-operate-not-just-to-route" class="hash-link" aria-label="Direct link to Built to operate, not just to route" title="Direct link to Built to operate, not just to route" translate="no">​</a></h2>
<table><thead><tr><th>Capability</th><th>What you get</th></tr></thead><tbody><tr><td><strong>Capacity</strong></td><td>Per-group <code>maxRunningQueries</code>; with Postgres persistence, one limit across replicas</td></tr><tr><td><strong>Health</strong></td><td>Continuous checks; unhealthy clusters drop out and rejoin automatically</td></tr><tr><td><strong>Observability</strong></td><td>Prometheus metrics and a bundled Grafana dashboard</td></tr><tr><td><strong>Studio</strong></td><td>Live cluster health, searchable query history with routing traces, config via admin API</td></tr><tr><td><strong>Live config</strong></td><td>Routing rules and clusters reload without restarts when persistence is enabled</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="from-one-copy-many-reads-to-one-query-many-execution-paths">From "one copy, many reads" to "one query, many execution paths"<a href="https://queryflux.dev/blog/why-queryflux#from-one-copy-many-reads-to-one-query-many-execution-paths" class="hash-link" aria-label="Direct link to From &quot;one copy, many reads&quot; to &quot;one query, many execution paths&quot;" title="Direct link to From &quot;one copy, many reads&quot; to &quot;one query, many execution paths&quot;" translate="no">​</a></h2>
<p>The lakehouse already embraced one copy of data with many readers. QueryFlux extends the idea to execution: <strong>one query interface, many execution paths</strong>.</p>
<p>Instead of asking which engine a team should use forever, or which dialect to write in, you can ask which engine fits this workload, whether you can fall back when one struggles, and how to keep application code stable while the engines underneath change.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-open-source">Why open source<a href="https://queryflux.dev/blog/why-queryflux#why-open-source" class="hash-link" aria-label="Direct link to Why open source" title="Direct link to Why open source" translate="no">​</a></h2>
<p>If the data world moved to open table formats to escape lock-in, it makes no sense to solve compute interoperability with a proprietary black box in the middle. An open-source router is a neutral layer above the engines: a place for the community to add adapters and routing policies, and a system that stays composable instead of becoming another platform to be trapped in.</p>
<p>Open data deserves open compute coordination.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-bigger-opportunity-agents">The bigger opportunity: agents<a href="https://queryflux.dev/blog/why-queryflux#the-bigger-opportunity-agents" class="hash-link" aria-label="Direct link to The bigger opportunity: agents" title="Direct link to The bigger opportunity: agents" translate="no">​</a></h2>
<p>Once queries can be normalized, understood, and routed across engines, you have a system that decides where work runs and how. That matters for analytics, and just as much for the AI agents and application backends that increasingly depend on live data.</p>
<p>Agents don't use data the way dashboards do. They explore, retry, and reformulate, generating SQL against systems they don't fully understand. If every engine stays a silo, every agent has to learn engine-specific dialects, limits, performance trade-offs, and connection logic. That is a poor foundation.</p>
<p>Sitting above the engines, QueryFlux gives agents one stable interface: an MCP endpoint or any SQL driver. It records which agent and conversation every query came from, and puts the same guardrails and access policies in front of agents as in front of everyone else. The agent says what it wants; QueryFlux decides where it runs.</p>
<p>Read next: <strong><a class="" href="https://queryflux.dev/blog/agents-sql-not-keys">Give agents SQL, not the keys to every engine</a></strong>.</p>
<div class="blog-cta"><p><a class="" href="https://queryflux.dev/docs/getting-started">Get started</a> · <a class="" href="https://queryflux.dev/docs/configuration">Configuration</a> · <a href="https://github.com/lakeops-org/queryflux" target="_blank" rel="noopener noreferrer" class="">GitHub</a></p></div>
<p>One copy of data. Many readers. One query. Many possible engines.</p>]]></content:encoded>
            <category>Vision</category>
        </item>
    </channel>
</rss>