Who this is for
- BI Analysts and dashboard developers who want fast, responsive dashboards.
- Data modelers shaping data for reporting.
- Team leads who need a simple, repeatable way to improve performance across many dashboards.
Prerequisites
- Basic SQL (SELECT, WHERE, GROUP BY) and understanding of joins.
- Familiarity with your BI tool’s data model (star schema, extracts/caches, calculated fields).
- You can view and edit dashboards and their data sources.
Why this matters
BI dashboards often become the daily control panel for business decisions. If they take 10–30 seconds to load, users stop trusting them—or stop using them. As a BI Analyst, you’ll routinely:
- Audit slow dashboards and cut initial load to under 3–5 seconds.
- Move heavy calculations upstream (database, ETL) to lighten the BI layer.
- Design aggregates/extracts to keep the UX fast while preserving detail when needed.
- Set a performance budget so new features don’t degrade speed.
Concept explained simply
Think of a dashboard like a café order:
- Source: Ingredients in the kitchen (database tables).
- Query: The order ticket (SQL generated by visuals).
- Model: Prep station (joins, relationships, calculated columns).
- Compute: Cooking (aggregations, calculations, filters).
- Visualize: Plating (charts rendering).
Load time suffers when any station is overloaded. Your job is to move work earlier, simplify what’s asked, and reuse results.
Mental model: The 4 Levers
- Less data: Select fewer rows and columns (filters, pre-aggregations).
- Cheaper data: Indexes, partitions, materialized views, extracts/caches.
- Fewer visuals: Each tile triggers queries; reduce or delay them.
- Lighter calculations: Move expensive calculations upstream; avoid row-by-row logic in the BI tool.
Performance workflow (repeatable)
- Measure baseline: Time to first render, total queries, longest query, rows scanned, total visuals. Capture a before screenshot and numbers.
- Kill obvious waste: Remove hidden/unused visuals, unused fields, and over-detailed columns. Limit default date range (e.g., last 90 days).
- Pre-aggregate for the landing view: Create an aggregate table or extract for the default KPIs. Keep a drill path to detail.
- Tune the source: Add indexes on join/filter keys, use partitions on date. Use only needed columns.
- Simplify calculations: Replace row-level custom logic with precomputed columns; prefer aggregates at the database layer.
- Defer and cache: Defer secondary tiles until interaction; enable query caching/extract refreshes on schedule.
- Re-measure and set a budget: E.g., home view under 3 seconds, interactions under 1 second. Document what changed.
Tip: Choosing between live and extract/cached data
- Live: best when data must be real-time and you have a fast warehouse with indexes/partitions.
- Extract/Cache: best when users don’t need second-by-second updates and source queries are expensive.
Worked examples
1) Shrink the landing view with pre-aggregation
Problem: The home tab scans a 120M-row fact table for 6 tiles. First render: ~18s.
Fix:
- Create a daily aggregate table: columns = date, product_id, region_id, revenue, units.
- Make home tiles read from this aggregate; keep a drill-through to the detail fact.
- Limit default date range to last 90 days.
Result: First render drops to ~2.8s. Detail exploration still available via drill-through.
2) Tame a high-cardinality filter
Problem: A customer name filter (1.5M values) freezes. Slicers become unusable.
Fix:
- Replace free-text name filter with Customer Tier and Region filters to narrow first.
- Add an indexed surrogate key; use a search box only after narrowing.
- Hide low-value dimensions from the default state; reveal in a drill panel.
Result: Filter interaction drops from ~6s to ~0.8s.
3) Move expensive calculations upstream
Problem: A tile computes a complex margin formula per row with multiple case expressions.
Fix:
- Create a precomputed margin column in the warehouse or ETL.
- Aggregate at the source (GROUP BY) to the required grain (e.g., day x product x region).
Result: Longest query drops from ~9s to ~1.5s, dashboard initial load from ~12s to ~3.5s.
Common mistakes and self-check
- Mistake: Optimizing visuals before fixing data volume. Self-check: Are you scanning only the rows needed for the landing state?
- Mistake: Too many tiles on first render. Self-check: Count tiles and queries; can you defer some until interaction?
- Mistake: High-cardinality slicers as defaults. Self-check: Replace with hierarchical/narrowing filters.
- Mistake: Complex row-level formulas in the BI layer. Self-check: Can this be precomputed upstream?
- Mistake: Missing indexes on join/filter keys. Self-check: Longest queries—do they filter on non-indexed columns?
- Mistake: Wide SELECT *. Self-check: List columns; keep only those actually used by visuals.
Quick self-audit checklist
- Initial load under 3–5 seconds.
- No more than 8 tiles on the landing view.
- Default date range limited.
- Aggregates or extracts for landing KPIs exist and are used.
- Join/filter keys indexed; large tables partitioned by date.
- High-cardinality filters replaced or narrowed.
- Heavy calculations precomputed upstream.
Practical projects
- Project 1: Pick a slow dashboard and cut initial load time by 50%. Document baseline, changes, and after metrics.
- Project 2: Build an aggregate table/extract for the top 3 KPIs and add a drill-through to detail.
- Project 3: Replace a high-cardinality slicer with a two-step narrowing filter approach.
Exercises
These match the exercises below; complete them here, then compare with the solutions.
Exercise 1: Diagnose and plan
- Measure current performance: initial load time, number of tiles, longest query, rows scanned.
- Propose three specific changes using the four levers (less data, cheaper data, fewer visuals, lighter calculations).
- Estimate expected impact for each change (e.g., “-50% rows scanned”).
Hints
- Start by reducing default date range and removing hidden tiles.
- Use an aggregate for the landing page and keep drill-through.
Exercise 2: Rewrite a slow query
Given a slow query that scans a full table, produce an aggregated version and list which indexes/partitions would help.
-- Original (slow)
SELECT o.order_id, o.order_date, o.region, d.product_id, d.qty, d.price
FROM orders o
JOIN order_details d ON d.order_id = o.order_id
WHERE o.order_date >= '2024-01-01';
Rewrite to only return daily revenue by region and product for the landing page.
Hints
- Aggregate by date, region, product_id.
- Add an index on order_date and region (or partition by order_date).
Progress note
Quick test is available to everyone. Only logged-in users get saved progress.
Learning path
- Start: Measure and document baseline performance.
- Next: Implement aggregates/extracts for the home view.
- Then: Tune source (indexes, partitions) and simplify calculations.
- Finally: Defer secondary visuals and set a performance budget.
Next steps
- Apply these steps to one production dashboard this week.
- Create a team performance checklist and add it to your dashboard PR/review process.
- Schedule extract/aggregate refreshes aligned with data latency needs.
Mini challenge
Take a dashboard that loads in 12–20 seconds. In one day, cut it under 4 seconds without removing critical KPIs. Constraints: keep existing KPIs, allow drill to detail, document every change and its impact.