Interactive Warehouse Benchmark

Solution: IWB_202609091200  ·  TPC-H SF100 (150M orders, 15M customers)  ·  2026-09-09  ·  50 concurrent users  ·  3-minute run

Executive Summary

P95 — Snowflake (server-side)
816 ms under goal
P50 — Snowflake (server-side)
498 ms under goal
P50 — Client (end-to-end)
530 ms under goal
Throughput
31.6 req/s
Total requests
5657
Failures
0 / 5657 100% success
Goal met. With 50 concurrent dashboard users hitting five query variants (variable date range, nation, region and market filters) at ~31.6 req/s, both client-side P95 (850 ms) and Snowflake server-side P95 (816 ms) stay under the 1000 ms target. Zero failures, zero queueing, zero fallback hits. Winning configuration reached in one scale-up step: Interactive Small warehouse with MIN=7 / MAX=10 clusters, zero-copy over the source tables in DM_TESTTPCH_BENCH_DB.TPCH_SF100.

Benchmark Setup

Connection
PM
Database / schema (interactive)
DM_TESTTPCH_BENCH_DB.TPCH_SF100
Interactive warehouse
IWB_202609091200_BENCH_WH_INT · Small · MIN=7 / MAX=10 clusters · STANDARD scaling · fallback IWB_202609091200_BENCH_WH_STD (X-Small)
Clusters used
8 of 10 clusters served queries during the load test
Users
50 concurrent (locust --users 50 --spawn-rate 5)
Duration
3-minute run (180 s)
Load driver
SPCS BENCHMARK_LOCUST (Locust 2.45.0, auto-start / auto-quit)
API layer
SPCS BENCHMARK_API (FastAPI + Snowflake connector) · 4 uvicorn workers · POOL_SIZE=50 · CPU_X64_M · 3 instances
Result caching
USE_CACHED_RESULT=FALSE (measures Snowflake, not the result cache)
Query tag
QUERY_TAG='IWB_202609091200' (used for server-side isolation)

Query and Filter Variants

Locust rotates through the query variants uniformly at random per request, simulating a dashboard where the user changes the date range or applies filters.

SELECT
  N_NAME,
  COUNT(*) AS ORDERS
FROM ORDERS AS O
INNER JOIN CUSTOMER AS C ON O_CUSTKEY = C_CUSTKEY
INNER JOIN NATION   AS N ON C_NATIONKEY = N_NATIONKEY
WHERE O_ORDERDATE BETWEEN '1996-01-01' AND '1996-12-31'
GROUP BY ROLLUP (N_NAME)
ORDER BY N_NAME NULLS LAST;
VariantAdditional predicatePurpose
base (benchmark-query.sql)O_ORDERDATE BETWEEN '1996-01-01' AND '1996-12-31'Full-year rollup, no dimension filter — worst case for the interactive WH.
q2 — narrow dateO_ORDERDATE BETWEEN '1995-06-01' AND '1996-06-30'Different 13-month window — validates pruning on cold date ranges.
q3 — nation filterAND N_NAME = 'GERMANY'Dashboard filter on a single nation.
q4 — region filterAND R_NAME = 'EUROPE' (adds REGION join)Region roll-up — 5 nations, verifies extra join stays cheap.
q5 — market filterAND C_MKTSEGMENT = 'BUILDING'Customer market-segment filter (~20% of CUSTOMER rows).

Table Details (Interactive)

TableRowsBytes (GB)Cluster keyAligned with query predicates?
ORDERS150,000,0003.73LINEAR(O_ORDERDATE)Yes — matches the WHERE clause on O_ORDERDATE.
CUSTOMER15,000,0000.87LINEAR(C_CUSTKEY)Yes — matches the join key.
NATION25~0.00LINEAR(N_NATIONKEY)Yes — matches the join key (tiny table, effectively broadcasted).
REGION5~0.00LINEAR(R_REGIONKEY)Yes — only touched by q4; join key matches.

Total working set ≈ 4.6 GB. This is 1% of an X-Small warehouse's ~350 GB cache and fits comfortably in a Small warehouse's ~600 GB cache. All tables are already clustered on the columns used by the workload, so zero-copy is the right path — no interactive-table copies were needed.

Performance Results — Client-side (Locust HTTP)

End-user-observed latency: HTTP round-trip through the SPCS API + Snowflake execution.

EndpointRequestsreq/sMinP50P90P95P99Max
POST /api/run/interactive5,65731.61855307908509901,224

All values in milliseconds.

Performance Results — Server-side (Snowflake)

What Snowflake alone spent — compile + execute + queue — measured from INFORMATION_SCHEMA.QUERY_HISTORY_BY_WAREHOUSE over the run window, filtered to QUERY_TAG='IWB_202609091200'.

WarehouseNP50P90P95P99Avg compileAvg execAvg queueAvg MB scanClusters used
IWB_202609091200_BENCH_WH_INT (Small)5,678498762816966924170~1,4708 of 10

All values in milliseconds unless labelled. N is the count of SUCCESS queries observed.

Client vs Server — Bottleneck Diagnosis

The gap between the client-observed latency (Locust) and the server-side latency (Snowflake) tells us where to optimize.

PercentileLocust (client)Snowflake (server)Delta (API/HTTP)
P5053049832 ms
P9585081634 ms
P9999096624 ms

Analysis

Client-vs-server delta is small and constant across percentiles (24–34 ms). This lines up with the baseline test where the no-op endpoint returned P99=10 ms — so ~15–25 ms per request is round-trip HTTP + connection-pool acquire + response framing, and the rest is Snowflake time. The delta is flat, not growing with the percentile, so there is no worker/GIL saturation and no connection-pool contention on the API tier. Snowflake is doing effectively all of the observed work.

Verdict: Snowflake is the (small remaining) bottleneck. The API layer contributes ~25 ms of overhead — negligible against the 500–800 ms of Snowflake time. To push P95 lower you would need to reduce Snowflake time (larger warehouse, tighter clustering, pre-aggregated summary table). Adding more API instances or workers would not move the needle.

Client-Side Percentile Comparison

P50
530 ms
P95
850 ms
P99
990 ms

Server-Side Percentile Comparison (Snowflake alone)

P50
498 ms
P95
816 ms
P99
966 ms

Query Profile Health (Interactive, top-slowest)

QUERY_IDTotal (ms)Compile (ms)Execute (ms)Queued (ms)MB scanned
01c6f588-0e1a-1410-0009-7101b37f19171,204881,11601,505.6
01c6f588-0e1a-1410-0009-7101b37f18db1,1421011,04101,548.9
01c6f588-0e1a-1410-0009-7101b37f18cf1,141951,04601,548.9
01c6f588-0e1a-1410-0009-7101b37f18fb1,134971,03701,548.9
01c6f587-0e1a-1410-0009-7101b37f10e71,11912499501,548.9
01c6f588-0e1a-1410-0009-7101b37f606b1,10012197901,548.9
01c6f587-0e1a-1410-0009-7101b37f10c31,06510296301,548.9
01c6f588-0e1a-1410-0009-7101b37f2e5f1,06010096001,548.9
01c6f588-0e1a-1410-0009-7101b37f193b1,0568996701,505.6
01c6f588-0e1a-1410-0009-7101b37f2e731,0438096301,505.6

Verdict

Escalation Path

Each row is one benchmark iteration. If only one row is present, the target latency was met at the initial configuration and no scale-up or scale-out was required. If multiple rows are present, they show the sequence of Step 13c decisions that led to the reported numbers.

IterWH sizeMIN / MAX clustersClusters usedServer P95 (ms)Client P95 (ms)Throughput (rps)Queue avg (ms)Decision
1X-Small7 / 1010 of 101,6841,70025.70P95 goal missed by ~700 ms. No queueing observed → scale-out cannot help. Diagnosis: per-query execute time bound. Scale up X-Small → Small.
2Small7 / 108 of 1081685031.60Goal met. Server-side and client-side P95 both under 1000 ms. Stop.

Optimization Recommendations

To meet the latency goal

  1. Already met at Small × MCW 7-10 with zero-copy on the source tables. No further tuning is required for the current workload shape.

Already correct — keep these

General guidance for dashboards on Interactive

Configuration Used

Compute pools

  • IWB_202609091200_BENCH_API_POOL — CPU_X64_M · 1–4 nodes · hosts 3 API instances
  • IWB_202609091200_BENCH_LOCUST_POOL — CPU_X64_M · 1–2 nodes · hosts 1 Locust instance

API server (per uvicorn worker)

  • Framework: FastAPI + Snowflake Python connector
  • Instances: 3 · Workers per instance: 4 · POOL_SIZE=50
  • CPU: 2000m request / 4000m limit · Memory: 2Gi request / 4Gi limit
  • Endpoints: /api/run/interactive, /api/run/baseline, /api/health

Interactive warehouse

  • Name: IWB_202609091200_BENCH_WH_INT
  • Size: Small · MIN_CLUSTER_COUNT=7 · MAX_CLUSTER_COUNT=10 · SCALING_POLICY=STANDARD
  • AUTO_SUSPEND=86400 s (24 h) · AUTO_RESUME=TRUE
  • Attached tables: ORDERS, CUSTOMER, NATION, REGION (proactive cache)
  • Fallback: IWB_202609091200_BENCH_WH_STD (X-Small)

Locust

  • Version: 2.45.0 · Mode: non-headless with --autostart --autoquit
  • Users: 50 · Spawn rate: 5/s · Duration: 180 s
  • Target: http://benchmark-api:3000 (SPCS internal DNS)
  • Two-phase: baseline (/api/run/baseline) → benchmark (/api/run/interactive) with 5 SQL variants