SaaS dashboard scaling
SaaS dashboard scaling and launch checklist
A SaaS dashboard stays fast by paginating every list, indexing the main questions, and deferring heavy work until a measured trigger fires. At launch, apply 9 rules, watch 6 signals and hold the performance targets below.
Last checked
Rules to apply at launch
- Data growth: Every org-owned table has org_id as the leading column of its hot index and an RLS policy; no query is allowed without an org scope, so a single large tenant cannot degrade scans for others.
- Pagination: Every list (members, audit log, deliveries, API data) uses keyset pagination on an indexed (sort_key, id) tuple with a cursor token; no OFFSET; page size defaults to 25 and is capped at 100.
- Caching: Dashboards read pre-aggregated metrics_daily and usage_rollups, never count(*) over raw tables; every chart is a single range query bounded to <= 366 points.
- Caching: Public pages (/pricing, docs) are statically rendered with edge caching; authenticated pages send Cache-Control: private, no-store and use server-side per-org caches (Next.js cache tags) invalidated on writes, TTL <= 60 s.
- Rate limits: Per-API-key and per-org token-bucket limits (plan-based requests per minute and per day) stored in a Postgres rate_limits table with atomic UPDATE ... RETURNING; auth endpoints are limited per IP and per account; limit hit returns 429 with Retry-After.
- Fanout: Usage events are buffered in process and flushed in one multi-row INSERT every 5 s or 500 rows, so metering adds <= 1 write per flush per worker, not one per API call.
- Search: In-app search over members, audit entries and resources uses tsvector + GIN or pg_trgm scoped by org_id; a dedicated search service is deferred.
- Storage: Org uploads (logos, exports, attachments) go via presigned PUT to object storage under `org/<org_id>/` with per-plan byte quotas checked before the URL is issued.
- Realtime: Live dashboard widgets use polling with ETag/If-None-Match every 15-30 s or an SSE stream fed by LISTEN/NOTIFY; a dedicated websocket tier is deferred.
Performance targets
| What to measure | Target |
|---|---|
| Dashboard page (authenticated) TTFB | p95 < 400 ms with <= 12 queries, every chart one indexed read |
| Public API request | p95 < 150 ms for reads, p99 < 400 ms for writes, <= 5 queries |
| Usage ingest | p99 < 100 ms per batch, one INSERT per batch |
| Limit check (assertWithinLimit) | p99 < 10 ms, one primary-key read |
| Webhook delivery latency | p95 < 30 s from event to first attempt |
| Audit log page | p95 < 300 ms at 10M rows per org via keyset index |
What to watch
- Metric api request duration ms: histogram by route, status class and plan; powers the latency SLO.
- Metric usage events flush lag s: age of the oldest unflushed usage buffer and rollup lag.
- Metric webhook delivery success ratio: per endpoint and global; consecutive-failure counter drives auto-disable.
- Log structured request and audit logs: request id, org id, actor id hash, route, permission decision; no tokens, keys or email addresses.
- Trace request to webhook delivery trace: one trace id from the domain event to the signed POST.
- Slo api availability: 99.9% of authenticated API requests succeed; error budget reviewed monthly.
Recurring work
- rollup_usage: runs cron 1m. Guard: bounded batch by id range, upsert by (org_id, meter, period_start), advisory lock so two runs never overlap.
- deliver_webhook: runs queue (webhook_deliveries). Guard: SSRF re-check, 10 s timeout, attempt cap 8, response body truncated to 2 KB, per-endpoint concurrency 1.
- build_metrics_daily: runs cron hourly + nightly backfill. Guard: recompute only today and yesterday hourly; idempotent upsert; bounded per-org work.
- export_audit_csv: runs queue (jobs kind=export_audit). Guard: streams to object storage in chunks, max rows and bytes per export, one running export per org, signed link expires in 24 h.
- sweep_trial_and_dunning: runs cron daily. Guard: reads subscriptions by (status, current_period_end) in batches; sends at most one reminder per stage per org.
Add when the numbers say so
- Read replicas: Route dashboard, audit and export reads to a read replica once primary CPU or read p95 crosses the trigger; reads and writes already use separate repository functions so this is configuration.
- Fanout: Move outbound webhook fan-out and usage ingest to a dedicated event bus once sustained ingest exceeds what one Postgres queue handles.
Deferred until it is needed
| Deferred | Adopt it when |
|---|---|
| Custom roles and fine-grained permissions | 3+ customers ask for custom roles. |
| Dedicated event bus for webhooks and ingest | sustained ingest > 2k events/s or queue claim p95 > 100 ms. |
| Read replicas for dashboards and audit | primary CPU > 60% sustained or dashboard read p95 > 300 ms. |
| Columnar analytics store | ad-hoc customer analytics scans > 100M rows per query. |
On Forge64
Forge64 matches your description to one of 12 reference architectures, writes the plan and data model into the project, generates the app, and runs a quality gate over the code before you see it. Each app gets its own Postgres database and sign-in with no per-user fee. Hosting on Forge Cloud and Forge Payments are rolling out.
Every metered resource on Forge64 has a hard cap, so a traffic spike stops at your limit instead of becoming a surprise bill. This is a reference design for the category, not a promise that every item is generated today. Built-ins such as email, background jobs, file uploads and search are coming soon, so anything here that needs them is something you add yourself for now.
FAQ
- When should I worry about scale for a SaaS dashboard?
- Not before a measured trigger fires. For example, for "Custom roles and fine-grained permissions": 3+ customers ask for custom roles.
- What performance targets should a SaaS dashboard aim for?
- Dashboard page (authenticated) TTFB: p95 < 400 ms with <= 12 queries, every chart one indexed read. Public API request: p95 < 150 ms for reads, p99 < 400 ms for writes, <= 5 queries. Usage ingest: p99 < 100 ms per batch, one INSERT per batch.
- Does Forge64 cap my usage costs?
- Yes. Every metered resource has a hard cap, and paid plans add an overage spend cap that you set.
Build your app with Forge64
One prompt to a running app with auth, database, hosting and payments. Start free, no credit card required.
Start building for free