Dashboard Performance
A dashboard's perceived speed is determined by three independent things: how many charts have to render, how many queries the backend can execute concurrently, and how quickly the underlying data warehouse can return results. Superset gives you levers for the first two; the third belongs to your warehouse. This page covers the dashboard-side levers and the practical guidance around them.
Is there a maximum chart count per dashboard?
No hard limit is enforced — Superset has no configuration key that caps the number of charts on a dashboard. In practice, dashboards behave well up to a few dozen charts. Beyond that, you'll typically feel friction on the initial load and during cross-filter / time-range updates, even with the lazy-loading optimizations described below.
Rough thresholds to keep in mind:
- Under ~25 charts: usually no perceptible problem.
- 25–50 charts: still fine, but you start to want tabs to break the page into chunks the user actually looks at.
- Over ~50 charts: split into multiple dashboards or use tabs aggressively. The bottleneck is rarely Superset itself — it's the warehouse executing dozens of queries in parallel and the browser rendering dozens of chart frames.
These are guidelines, not guarantees. A dashboard of 100 sparkline-style charts hitting a fast cache behaves very differently from a dashboard of 20 heavy aggregations against a cold warehouse.
Lazy rendering — DASHBOARD_VIRTUALIZATION
Superset's dashboard layout is virtualized at the row level. Charts that are far below the user's current scroll position render a placeholder instead of their visualization until the user scrolls them into view, and go back to a placeholder if scrolled well past. The chart component itself stays mounted throughout — only the visualization is swapped for a placeholder — so this alone does not reduce backend query load; see Deferred data fetch below for that. This is on by default.
Feature flag: DASHBOARD_VIRTUALIZATION (default: True)
The flag is stable and marked for path-to-deprecation — meaning the
behavior will eventually be non-optional, but the flag still exists so
operators can disable it if a specific layout misbehaves.
Behavior (from superset-frontend/src/dashboard/components/gridComponents/Row/Row.tsx):
- A chart's visualization is rendered when its row scrolls within 1 viewport height of the visible area.
- A chart's visualization is swapped back for a placeholder when its row scrolls more than 4 viewport heights away from the visible area.
- Tabs that aren't currently selected don't render their content at all (see below).
- The placeholder-swap-back is skipped in embedded mode (so an embedded dashboard keeps its charts rendered once they've been seen, which avoids re-rendering on scroll-up). Both halves are skipped for headless / bot rendering (so screenshot / report jobs load every chart).
Deferred data fetch — DASHBOARD_VIRTUALIZATION_DEFER_DATA
By default, DASHBOARD_VIRTUALIZATION only controls whether a chart's
visualization is rendered — the chart component still mounts and issues
its data request immediately, regardless of scroll position.
DASHBOARD_VIRTUALIZATION_DEFER_DATA is a supplementary flag that skips
the data request itself for charts that aren't currently in view, useful
for backends where opening a connection or compiling a query is expensive
even if the result would be thrown away. It only has an effect when
DASHBOARD_VIRTUALIZATION is also enabled — with virtualization off,
every chart is treated as in view, so there's nothing left to defer.
Feature flag: DASHBOARD_VIRTUALIZATION_DEFER_DATA (default: False)
Enable this if you see warehouse load spike on dashboard open even though most charts are off-screen.
Per-tab lazy loading
This is on by default and has no flag. A tab's content is not rendered until the user activates that tab, so charts inside an unselected tab do not fetch data on dashboard open. When the user clicks the tab, that tab's charts mount and fetch in the normal way.
Practically: tabs are the single most effective tool for a large dashboard. Splitting 60 charts across 4 tabs effectively turns dashboard open into "load ~15 charts," and the remaining ones lazy-load only if the user goes looking.
Is there a switch to cap concurrent chart queries?
No. Superset does not implement a frontend-side concurrent-request limiter. Each chart issues its own data request when it mounts, and the browser handles parallelism — typically ~6 in-flight requests per origin under HTTP/1.1, though HTTP/2 or HTTP/3 (if your deployment terminates TLS that way) can multiplex considerably more over a single connection. Backend throughput is bounded by your Gunicorn worker count for synchronous query execution, or by your Celery worker pool when async queries are enabled.
If you need to throttle warehouse load, the right place is:
- The warehouse itself (connection pool / concurrency limits).
- Superset's Celery configuration (smaller worker pool when async queries are on).
- Splitting heavy charts across tabs or separate dashboards (each dashboard load only fetches what's visible).
Splitting strategies
When a dashboard outgrows comfortable performance, the options in order of effort:
1. Move sections into tabs. Same dashboard, but only the active tab's charts fetch. This is the cheapest change and often the only one needed.
2. Cache aggressively. A Redis cache backend (see Caching) means repeat dashboard loads serve from cache rather than re-hitting the warehouse. This is especially impactful for dashboards opened by many users in close succession.
3. Enable async queries. Async query execution via Celery decouples query duration from request lifetime, so a slow chart doesn't block the page. The user sees other charts come in as their queries complete.
4. Split into multiple dashboards. Group related charts into purpose- specific dashboards rather than one mega-dashboard. Link them from a landing dashboard or a navigation menu.
5. Pre-aggregate at the warehouse level. If the same expensive aggregation appears across many charts, materialize it as a view or scheduled table in the warehouse so each chart query is a cheap lookup.
Operational notes
-
The feature flags above are set in
superset_config.py, e.g.:FEATURE_FLAGS = {"DASHBOARD_VIRTUALIZATION": True,"DASHBOARD_VIRTUALIZATION_DEFER_DATA": True,} -
See Feature Flags for the full list of supported flags and their lifecycle stages.
-
Server-side screenshot jobs (alerts, scheduled reports, thumbnails) render the dashboard in a headless, webdriver-controlled browser, which intentionally bypasses row virtualization so the rendered artifact includes every chart, not just the ones above the fold. User-triggered "download as image/PDF" is different: it captures whatever's currently rendered in the user's own browser, so it's still subject to virtualization like any other page view. Metadata/YAML dashboard export doesn't render the frontend at all, so virtualization doesn't apply to it either.