Snowflake interactive warehouses: cheaper, always-warm, and yes, the SQL API works

Snowflake’s interactive warehouses sit in a sweet spot for user-facing workloads: they cost 60% of a same-sized standard warehouse, can stay warm across business hours, and still serve sub‑second lookups from an interactive table

Share
Snowflake interactive warehouses: cheaper, always-warm, and yes, the SQL API works

TL/DR: ❄️ Snowflake interactive warehouses cost 60% of a same‑sized standard warehouse, can stay warm across business hours, and (despite the docs staying quiet on this) work just fine behind the SQL REST API.


I just wired one up to power a Salesforce Lightning Web Component (LWC): a per-contact touchpoint timeline on the Contact record page, reading from a curated Snowflake view via the SQL API:

The touchpoint timeline embedded in Salesforce
Previously I used an embedded Tableau dashboard for the same purpose, but replaced it for two main reasons. Even though Tableau dashboards (running on Tableau Cloud in my case) are easy to embed in Salesforce, they (1) require a license for each Salesforce user and (2) add a lot of overhead to the embed: authentication passes through Tableau Cloud, the dashboard loads data from either the underlying DWH or a data extract, and then the dashboard is rendered. This makes them both expensive and slow

The LWC needed a backend that could stay warm across business hours without burning credits and still answer a selective lookup in well under a second.

Why an interactive warehouse (and not a standard one)

With a standard warehouse I had two options: keep it warm all day (and pay the full credit rate for a warehouse that is mostly idle between clicks) or let it auto-suspend and eat a cold-start delay every time a user opened a Contact record page in Salesforce. Neither really fits a user-facing surface.

Interactive warehouses side-step this tradeoff. They are (currently) billed at 60% of a same-sized standard warehouse per Snowflake's credit consumption table, which turns "always warm across business hours" into an viable option at XS size.

Snowflake's own docs on interactive warehouses have a compact section on cost and billing:

Snowflake interactive tables and interactive warehouses | Snowflake Documentation

The interactive warehouse (and the interactive table attached to it) effectively function as a middle layer for the LWC.

Any middle layer that does not plug into the curated Snowflake view directly requires some duplication of the data, but with an interactive warehouse this is trivial. This becomes particularly relevant if the data is refreshed more often than once per day

Of course, there are plenty alternative options for such a middle layer, but I wanted to test interactive warehouses anyway, so let's stick with them. Still, here are some alternatives I considered:

  • duckdb-wasm in the LWC connecting to Motherduck, but Salesforce doesn't set the COOP/COEP headers Wasm needs, and I didn't find any knob for it
  • PostgreSQL + PostgREST should work, just like Supabase or Neon would, too
  • Substituting PostgREST with a worker (for example on Cloudflare or Fly.io), with a backend database that could be almost anything

Anyway, back to interactive warehouses...

The setup: warehouse, interactive table, binding

Interactive warehouses can only query interactive tables: not standard tables, not views, not hybrid tables. So the setup is three steps: create the warehouse, materialize the data as an interactive table, and bind the two together.

First the warehouse itself:

CREATE INTERACTIVE WAREHOUSE WH_INTERACTIVE WITH
    WAREHOUSE_SIZE = 'XSMALL'
    AUTO_SUSPEND = 86400        -- 24h minimum
    AUTO_RESUME = TRUE
    INITIALLY_SUSPENDED = TRUE;

GRANT USAGE ON WAREHOUSE WH_INTERACTIVE TO ROLE READER;

XS is the smallest size available. For my workload (per-contact lookups over 16.5M rows clustered on the right key) it is already plenty, so I can easily add additional workloads.

The minimum AUTO_SUSPEND for an interactive warehouse is 86400 seconds (24h). That is the smallest value the warehouse itself accepts. For finer control (e.g. suspend at 18:00 every evening), a separate scheduled ALTER WAREHOUSE ... SUSPEND task is needed.

Next, the interactive table. I materialize it from an existing curated view, clustered on the two columns I actually filter by:

CREATE OR REPLACE INTERACTIVE TABLE CONSUME.SALESFORCE_EMBED.IT_CONTACT_TOUCHPOINTS
    CLUSTER BY (id, date)
AS
SELECT id, date, touchpoint, label, link, icon
FROM CONSUME.SALESFORCE_EMBED.V_CONTACT_TOUCHPOINTS
WHERE date >= DATEADD(day, -400, CURRENT_DATE);

The 400-day trailing window fits the UI use case: the timeline only renders recent touchpoints. Clustering by (id, date) is what makes per-contact reads actually interactive (assuming good clustering depth): the engine touches only the micro-partitions for one contact instead of scanning the whole table.

Refreshing this table could use a target lag, a Snowflake task, dbt, or whatever is used to refresh tables 😎

Finally, the binding step:

ALTER WAREHOUSE WH_INTERACTIVE
    ADD TABLES (CONSUME.SALESFORCE_EMBED.IT_CONTACT_TOUCHPOINTS);

This one is easy to miss. Without it, queries come back with 010402 Table IT_CONTACT_TOUCHPOINTS is not bound to the current warehouse. The binding also doesn't survive CREATE OR REPLACE, so if the table is refreshed by rebuilding, the refresh task has to re-run the ALTER WAREHOUSE line after rebuilding the table.

Does the SQL REST API actually work against one?

The Snowflake docs list a handful of supported access paths for interactive warehouses (worksheets, Snowsight, a few client libraries) and pointedly do not mention the SQL REST API. But that is exactly what my Salesforce Apex (underlying the LWC) calls out to. If the SQL API did not work against interactive warehouses, this whole architecture would have fallen over.

So I tested it. I issued a Programmatic Access Token for a dedicated service user, attached a network policy allowing my Salesforce instance (sandbox and production pod's outbound IPs) in, and fired a POST /api/v2/statements with warehouse: WH_INTERACTIVE and the user's role in the body.

It works: Sub-second end-to-end from the Lightning Web Component to Snowflake and back, over a perfectly ordinary Apex HTTP callout.

For anyone landing here after Googling "snowflake interactive warehouse sql api": yes, it works. I tested it end-to-end from a Salesforce Apex callout against my own interactive warehouse. The docs just have not caught up yet.

Worth it?

The shape is finally obvious to me: interactive warehouse + interactive table + SQL API = (relatively) cheap, warm, sub-second reads from any HTTP-speaking caller.

And that's it: a small feature with a 40% price cut, a quirky binding model (well, replicating to an external system has its quirks, too), and full SQL API support. I'll be reaching for interactive warehouses whenever I need warm, cheap, sub-second reads from a user-facing surface. 🤓