Keeping the Dashboard Fast as Your Data Grows
Keeping the Dashboard Fast as Your Data Grows
Release v1.0.47 · Performance · Database
The Problem
Two of the most frequently hit queries in the application had no database indexes to back them:
-
Recent Activity feed — The dashboard's
recentActivityquery fetches the latest entries from theaudit_logtable, filtered byorgIdand sorted bycreatedAt DESCwith aLIMIT. Without an index covering both columns, Postgres had to filter all rows for an organisation, sort the entire result set in memory, and then discard most of it to satisfy theLIMIT. -
Notification bell — On every render, the notification bell issues a query against the
notificationstable filtered byorgId,userId, andreadstatus. Again, no index meant a sequential scan through every notification row matching the organisation.
Both of these queries run on every dashboard page load, making them high-frequency targets. Because both tables grow without bound — every action creates an audit log entry, every system event creates a notification — query cost was set to grow linearly with platform usage.
The Fix
Two composite indexes were added in src/db/schema.ts:
// Speeds up: SELECT ... FROM audit_log WHERE orgId = ? ORDER BY createdAt DESC LIMIT ?
index('audit_log_org_created_idx').on(auditLog.orgId, auditLog.createdAt)
// Speeds up: SELECT ... FROM notifications WHERE orgId = ? AND userId = ? AND read = ?
index('notifications_org_user_read_idx').on(notifications.orgId, notifications.userId, notifications.read)
Why Composite Indexes?
audit_log_org_created_idx (orgId, createdAt)— The leading columnorgIdnarrows the scan to a single organisation's rows. The trailingcreatedAtmeans those rows are already sorted, so Postgres can satisfy theORDER BY ... LIMITwith an index scan rather than a sort step.notifications_org_user_read_idx (orgId, userId, read)— All three filter columns are covered in a single index. Postgres can resolve the entireWHEREclause from the index without touching the table heap for the majority of query executions.
Impact
| Query | Before | After |
|---|---|---|
Dashboard recentActivity | Full sort on filtered rows | Index range scan + pre-sorted |
| Notification bell render | Sequential scan | Index-only lookup |
| Estimated latency saving | — | 10–30ms per load |
The saving is modest today but compounds over time — as audit logs and notifications accumulate across all landlord organisations on the platform, these indexes prevent what would otherwise become an increasingly expensive sequential scan.
No Action Required
This is a schema migration change. The indexes are applied automatically as part of the standard deployment process. There is no configuration change or action required by users or administrators.