Skip to content
Notifications
Clear all

KPI dashboards are loading way too slow - anyone else?

14 Posts
14 Users
0 Reactions
35 Views
(@devops_rookie_2025)
Prominent Member
Joined: 4 months ago
Posts: 467
Topic starter   [#23804]

Hey everyone! I've been setting up our KPI dashboards using Metabase on top of a PostgreSQL data warehouse. We're not huge—maybe 50k monthly visitors—but the dashboards have started loading painfully slow, sometimes taking over 30 seconds. It's mostly marketing funnel and campaign attribution data.

I'm still pretty new to all this. Could someone explain in beginner-friendly terms where I should start looking? Is it the database queries, the dashboard tool itself, or maybe how we're connecting them? Here's a sample query pattern I see running a lot:

```sql
SELECT date, campaign_id, COUNT(user_id)
FROM sessions
WHERE date > NOW() - INTERVAL '30 days'
GROUP BY 1, 2;
```

Thanks for any pointers! 🙏



   
Quote
(@alexw)
Reputable Member
Joined: 3 months ago
Posts: 443
 

That query pattern is a good clue. Counting distinct users over 30 days of session data can get heavy fast, even with 50k visitors, because each visitor might have many sessions.

Start by checking if there's an index on the date column in your sessions table. If not, adding one is the quickest win. You can also ask Metabase to show you the slow query log, which often points straight to the problem.

Beyond that, you might consider pre-aggregating that daily campaign data into a separate summary table that you update once a day. It's a common step when dashboards start to slow down.


Stay grounded, stay skeptical.


   
ReplyQuote
(@annaw)
Reputable Member
Joined: 3 months ago
Posts: 310
 

Great point about pre-aggregating. That's often the only way to make user-facing dashboards feel snappy.

Just want to add that when you create that summary table, make sure you also model it for how people *use* the dashboard. For example, if your team always filters by region or campaign type, bake those dimensions into the summary. It saves a ton of processing time versus trying to filter on the fly.

We hit this wall last year and moving to a daily-aggregated table cut load times from ~25 seconds to under 2. The trick is setting up a reliable refresh schedule so the data stays useful.



   
ReplyQuote
(@baller_analytics)
Honorable Member
Joined: 4 months ago
Posts: 483
 

That query is scanning every session row from the last 30 days. Counting users over a rolling window is always a resource hog.

Indexing the date column is a band-aid. You need to measure actual query execution time in PostgreSQL with EXPLAIN ANALYZE. Show the plan.

The real issue is your data model. You're asking for aggregated metrics on-demand. Build a daily aggregated table. Refresh it with a cron job. Serve the dashboard from that.


If it's not a retention curve, I don't care.


   
ReplyQuote
(@benjamink)
Estimable Member
Joined: 2 months ago
Posts: 202
 

Yeah, that sample query is a classic culprit. While indexes and summary tables help a lot, I'd also check if your Metabase questions are using its native query builder or if you've written custom SQL. The builder sometimes generates less efficient queries, especially with joins.

One thing that bit us was having a dashboard with, say, 10 cards that all looked at the last 30 days. Each one was firing its own query simultaneously, hammering the database. We started using Metabase's dashboard filters with a single date parameter, so all cards reuse the same filtered dataset. It cut down the concurrent load significantly.

Also, peek at your sessions table growth. 50k visitors can easily turn into millions of session rows over time. A simple `COUNT` on that table can start to drag.


automate everything


   
ReplyQuote
(@integration_ian)
Honorable Member
Joined: 5 months ago
Posts: 396
 

The dashboard filter tip is spot on - we saw the same improvement. But that only works if every card is pulling from the same underlying table.

If you've got a dashboard mixing data from sessions, orders, and ad spend, the shared filter doesn't help and you're back to concurrent queries. That's where you need to push aggregates to a single summary table first, *then* build the dashboard.


Integration is not a project, it's a lifestyle.


   
ReplyQuote
(@docker_diver)
Honorable Member
Joined: 4 months ago
Posts: 496
 

Yeah, that's the catch with dashboard filters. They're magic when everything comes from one place, but fall apart with mixed sources.

We tried building a "marketing overview" dashboard last month with cards from sessions, ad_spend, and conversions. Even with a shared date filter, it felt slow because Metabase was still hitting three different tables at once. Ended up creating a separate reporting schema with a single denormalized table that gets populated nightly. Now the whole dashboard is snappy.

How do you handle the nightly refresh though? Do you use a simple cron with a SQL script, or something like an Airflow DAG? I'm still figuring out the orchestration part.


Containers are magic, but I want to know how the magic works.


   
ReplyQuote
(@charlie2)
Reputable Member
Joined: 3 months ago
Posts: 345
 

Good point about dashboard filters cutting down concurrent queries. That worked for us too.

But I've noticed Metabase sometimes gets confused if the date column has a different name in each table, even when the filter is supposed to apply to all. Had to rename a few columns to get it to work smoothly.

What would you recommend if your tables are in different schemas?



   
ReplyQuote
(@devops_journeyman)
Reputable Member
Joined: 5 months ago
Posts: 216
 

Yeah, that's a classic query pattern that'll get you every time. The problem is you're asking the database to scan and count raw session rows on the fly, which gets heavier as your data grows.

First, check if that `date` column is indexed. If not, add one. That's the quickest fix you can do right now.

But the real fix is setting up a daily summary table. Schedule a job to run once a day that populates a new table with pre-aggregated counts, like `daily_campaign_summary`. Your dashboard query then becomes a simple `SELECT * FROM daily_campaign_summary WHERE date > ...`, which is almost instant.

You can start with a simple cron job running a SQL script. It's less intimidating than bringing in a full orchestration tool.



   
ReplyQuote
(@ci_cd_plumber_99)
Honorable Member
Joined: 7 months ago
Posts: 426
 

Spot on about the cron job being less intimidating. That's the right first step. But people always forget the second half, which is making that job robust enough to actually trust.

A simple cron SQL script will fail silently when the source table is locked or a column gets renamed. At a minimum, you need to log its runs and have some basic error handling that sends an alert. Otherwise you'll end up serving a dashboard from stale data for two weeks before anyone notices.

The jump from cron to Airflow or Prefect is huge, but you don't need to make it. Stick a `BEGIN...EXCEPTION...END` block in your script and pipe the cron output to a file. Check that file daily. It's ugly, but it's a start.


Speed up your build


   
ReplyQuote
(@datadog)
Reputable Member
Joined: 3 months ago
Posts: 365
 

That query is the problem. It's a full table scan every time.

Indexing the date column is a start, but you need to see the actual query plan. Run it with EXPLAIN ANALYZE and you'll see the cost.

The real fix is a daily rollup table. Run a nightly job to summarize the counts. Your dashboard query then becomes a simple lookup.


Metrics don't lie.


   
ReplyQuote
(@ethanp)
Reputable Member
Joined: 3 months ago
Posts: 371
 

That sample query is a textbook example of where performance issues begin. You've correctly identified the core pattern, which is a real-time aggregation scanning a large window of raw data.

The suggestions about indexing and summary tables are sound, but there's a diagnostic step before you jump into engineering solutions. In Metabase, check the "x-ray" feature on that question or the underlying table. It often highlights if a query is scanning an unusually high number of rows. This can confirm whether the slowness is isolated to this specific query logic or if it's part of a broader dashboard problem where multiple cards are firing similar heavy queries at once.

A simple but often overlooked point: ensure your Metabase instance itself has adequate resources. A dashboard loading slowly isn't always the database's fault. If the Metabase application server is under-provisioned, it can bottleneck while processing and rendering the results, even if the database query finishes moderately quickly.


Let's keep it constructive


   
ReplyQuote
(@infra_ops_learner)
Reputable Member
Joined: 5 months ago
Posts: 297
 

That's a great point about mixing data sources. So if I want a dashboard with, say, user logins and support tickets, I'd need to combine them into one summary table first for the shared filter to work properly?

How do you usually handle joining data from different systems for those nightly summary tables, especially if they're in different databases?


CloudNewbie


   
ReplyQuote
(@benjislack)
Reputable Member
Joined: 2 months ago
Posts: 244
 

Everyone's jumping to indexing and rollups. But the first thing I'd check is whether Metabase is generating a dozen versions of that same basic query for every card on your dashboard. It loves to do that.

Look at your dashboard in edit mode. If each card has its own date filter, you're hammering the database with near-identical heavy scans. Combine them into a single dashboard filter first. It's a two minute fix that might cut your load time in half.


your mileage will vary


   
ReplyQuote