CarbonSite
← Back to blog

Carbon Accounting at Scale: Why Your Dashboard Feels Slow

6 min readBy CarbonSite
performancedashboardsdatabasematerialized viewsemissions

Carbon Accounting at Scale: Why Your Dashboard Feels Slow

A sustainability manager loads the dashboard at a large manufacturing company (500 facilities, 100k activity records).

Loading time: 45 seconds.

By the time the dashboard renders, they've already switched to email. The dashboard is dead on arrival.

What happened? The dashboard query ran a SUM across all 100k activity records, grouped by category, for each user interaction (filter by date range, facility, emissions scope). At 45 seconds per query, the product is unusable.

CarbonSite's dashboard on the same data: 800ms.

The difference isn't engineering heroics. It's architectural fundamentals.

The Dashboard Query Problem

Here's what a naive emissions dashboard does:

SELECT 
  category, 
  SUM(emissions) 
FROM activity_records 
WHERE organization_id = 'org123' 
  AND reporting_period = '2025-12' 
  AND review_status = 'approved'
GROUP BY category;

At 100 activity records: instant. At 10,000 records: ~500ms. At 100,000 records: ~5 seconds. At 1,000,000 records (enterprise scale): ~45+ seconds.

Why? Each query scans every row, filters, groups, and sums. The database has to touch every record.

Add filters (facility, business unit, scope, date range), and you're running dozens of queries per dashboard load. Multiply by multiple users, and your database grinds to a halt.

This is why competitors' dashboards feel slow at enterprise scale.

The Solution: Materialized Views

CarbonSite pre-computes dashboard aggregates.

Instead of calculating on-demand, we maintain a DashboardAggregate table:

CREATE TABLE dashboard_aggregates (
  id UUID PRIMARY KEY,
  organization_id UUID,
  facility_id UUID,
  reporting_period YYYY-MM,
  category TEXT,
  scope_1 DECIMAL(15,2),
  scope_2 DECIMAL(15,2),
  scope_3 DECIMAL(15,2),
  total_co2e DECIMAL(15,2),
  record_count INT,
  last_updated TIMESTAMP
);

Every night (or after each calculation run), we refresh this table:

INSERT INTO dashboard_aggregates (organization_id, facility_id, reporting_period, category, scope_1, scope_2, ...)
SELECT 
  ar.organization_id,
  ar.facility_id,
  ar.reporting_period,
  ar.category,
  SUM(CASE WHEN emission_scope = 'scope_1' THEN co2e ELSE 0 END) as scope_1,
  SUM(CASE WHEN emission_scope = 'scope_2' THEN co2e ELSE 0 END) as scope_2,
  ...
FROM activity_records ar
WHERE ar.review_status = 'approved'
GROUP BY ar.organization_id, ar.facility_id, ar.reporting_period, ar.category;

Now, the dashboard query:

SELECT * FROM dashboard_aggregates 
WHERE organization_id = 'org123' AND reporting_period = '2025-12';

Query time: 800ms (vs 45 seconds on raw data).

Why so fast? Because the data is pre-aggregated. We're not summing 100k rows; we're selecting ~50 pre-computed rows.

Incremental Aggregation (The Real Magic)

Even better: we don't recalculate everything every time.

When a new activity record is approved, we update only the affected DashboardAggregate rows:

Record created: Facility #5, Dec 2025, Scope 1, 1,200 kg CO₂e
Update: dashboard_aggregates WHERE facility_id='#5' AND reporting_period='2025-12'
Delta: +1,200 kg to scope_1 column
Query time: 50ms

Instead of recalculating the entire organization's aggregates, we incrementally bump the affected cells.

This scales to millions of records without dashboard slowness.

Real-World Performance

Same customer (500 facilities, 100k+ records):

Dashboard ViewQuery Time (Before)Query Time (After)
Annual emissions by scope45 seconds200ms
Monthly trend (year)60 seconds300ms
Facility breakdown90 seconds500ms
Filtered (Q4 only, Scope 3)30 seconds150ms

Users went from "the dashboard is broken" to "the dashboard is instant."

The Monitoring Advantage

Materialized views also unlock monitoring:

SELECT 
  facility_id,
  SUM(total_co2e) as total,
  RANK() OVER (ORDER BY SUM(total_co2e) DESC) as rank
FROM dashboard_aggregates
WHERE reporting_period = '2025-12'
GROUP BY facility_id
ORDER BY rank;

Instant: "Which facilities are emitting the most? Rank them."

This query on raw activity records would take 30+ seconds at enterprise scale. On materialized views: 200ms.

Dashboards that used to be static (run once, wait for results) become interactive (click to drill down, instant results).

The Calculation Pipeline

Here's how incremental aggregation flows:

  1. Field worker submits a waste record (500 kg).
  2. Record is approved by sustainability manager.
  3. Trigger fires: Update DashboardAggregate for that facility + period.
    dashboard_aggregates.total_co2e += (500 kg × emission_factor)
    dashboard_aggregates.record_count += 1
    dashboard_aggregates.last_updated = NOW()
    
  4. Dashboard refreshes (real-time or polling every 30s).
  5. User sees the change immediately (or within 30 seconds).

No batch jobs. No waiting overnight for aggregates. Incremental updates.

The Scaling Strategy

ScaleActivity RecordsAggregates TableQuery TimeUpdate Time
SMB10k200 rows50ms10ms
Mid-Market100k2k rows200ms25ms
Enterprise1M20k rows500ms50ms

Even at 1 million records, dashboard queries are under 1 second because we're not scanning the activity table. We're reading pre-computed aggregates.

Indexes for Instant Filtering

Beyond materialized views, CarbonSite adds strategic indexes:

CREATE INDEX idx_activity_records_org_period ON activity_records(organization_id, reporting_period);
CREATE INDEX idx_activity_records_facility ON activity_records(facility_id);
CREATE INDEX idx_dashboard_aggregates_org_period ON dashboard_aggregates(organization_id, reporting_period);

With these indexes:

  • Filtering by facility: 10ms (not 5 seconds)
  • Date range queries: 50ms (not 30 seconds)
  • Drill-down from dashboard: instant (not slow)

The Real-Time Alternative (Coming Q2 2026)

Currently, dashboards refresh by polling (every 30 seconds). This is fast enough for most users.

But for executive dashboards, some customers want true real-time updates. CarbonSite's roadmap includes:

Server-Sent Events (SSE) — when a record is approved, the server pushes the update to all open dashboards (live subscription model).

WebSocket streams — real-time emissions tracking for executives monitoring facility performance.

These are only feasible because the underlying calculation is incremental and fast. Without materialized views, even SSE would be slow.

The Cost-Benefit

Materialized views add minimal complexity:

  • Extra table (~10MB per organization at 100k records)
  • Refresh logic (~50 lines of SQL)
  • Index maintenance (handled automatically)

Benefit:

  • 50x faster dashboards (45 seconds → 800ms)
  • 100x faster drill-downs
  • Scale to enterprise without re-architecting

Competitors who don't use materialized views will eventually hit a wall. When they reach 500k records, their dashboards become unusable. Then they have to retrofit materialized views (costly, risky).

CarbonSite is built for scale from day one.

The Operational Signal

If a competitor's dashboard is slow when you have 50k+ records, they're not using materialized views.

If their sales team says "our product can scale to enterprise," but their demo org has only 10k records, that's a red flag.

CarbonSite's demo org has 500k+ activity records, and the dashboard still loads in under a second.

The Path Forward

Carbon accounting at scale demands pre-computed aggregates, not real-time sums across millions of rows.

This is a solved problem in data engineering (materialized views, incremental updates), but it's surprisingly rare in carbon accounting platforms.


Performance at scale. See CarbonSite dashboards load in 800ms.

More from the blog

Read all posts →