Founder & Lead Engineer, RAITHub
RAITHub ships and tests production software. See QA as a Service or talk to us.
A HealthTech app that slows at clinic peak hours almost always has a slow database path, not a slow server. The usual causes, in order: a missing index on a patient or appointment query, an N+1 pattern loading records one by one, a connection-pool ceiling under concurrent staff, and a dashboard that recomputes on every load. Find which one with query logs before you change anything.
This is general engineering information, not compliance advice; keep production and patient data in your own covered cloud and confirm obligations with your adviser. If you would rather have it fixed for you, see how RAITHub would fix this below.
Why does my healthcare app slow down at 9am but not overnight?
Peak-hour slowness is a concurrency and data-volume problem. Overnight, one request runs against a small working set; at clinic open, dozens of clinicians, front-desk staff and the patient portal hit the same tables at once. A query that takes 40ms alone can take 4 seconds when fifty of them queue on one slow index scan or wait for a free database connection. The server CPU looks idle because the time is spent waiting, not computing.
| Symptom | Most likely cause | First check |
|---|---|---|
| Appointment list takes seconds to load | Missing index on the date or clinician column | Run EXPLAIN on the query |
| Patient page slow, gets slower with more records | N+1: one query per related row | Count queries per request in logs |
| App freezes under many staff, then recovers | Connection-pool ceiling | Pool size vs concurrent users |
| Reports or dashboards slow for everyone | Uncached aggregate recomputed per load | Is the result cached at all? |
| Slow only on serverless after idle | Cold starts and new connections | Latency of the first request |
How do I find the cause in an hour?
Measure before you guess. Add a health-check route that reports database latency and a sample query time, turn on slow-query logging, and count queries per request. A minimal health route in a Next.js API handler:
// app/api/health/route.ts
import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET() {
const start = Date.now()
// A cheap round-trip proves the connection and pool are healthy.
await prisma.$queryRaw`SELECT 1`
const dbMs = Date.now() - start
const qStart = Date.now()
// Swap in the query your clinic list actually runs.
const sample = await prisma.appointment.count({
where: { startsAt: { gte: new Date(new Date().setHours(0, 0, 0, 0)) } },
})
const queryMs = Date.now() - qStart
return NextResponse.json({ ok: dbMs < 100, dbMs, queryMs, sample })
}
If dbMs climbs under load, you are pool-starved. If queryMs is high even alone, the query needs an index or a rewrite. Confirm with the database's own plan:
EXPLAIN ANALYZE
SELECT id, patient_id, starts_at
FROM appointments
WHERE clinician_id = $1
AND starts_at >= date_trunc('day', now());
-- A "Seq Scan" on a large table is the signal to add an index.
The full Next.js version of this diagnosis is in why is my Next.js app so slow in production; the indexing detail is in the PostgreSQL indexing guide. If the slow screen is a patient-facing portal, the build patterns are in building a patient portal SaaS.
What are the fixes, in order?
Apply the lowest-cost high-impact fix first, retest, then move on. Do not add five changes at once; you will not know which one helped.
- Add the missing index. A composite index on the columns a hot query filters and sorts by, for example
(clinician_id, starts_at), often turns a seconds-long scan into milliseconds. Build it concurrently so it does not lock the table. - Kill the N+1. Load related records in one query (a join or an
include) instead of one query per row. A patient page that fired 1 + 50 queries becomes 2. - Size the connection pool. Serverless functions each open connections; a pooler (PgBouncer, or Neon's and Supabase's built-in poolers) keeps the database from hitting its connection limit at peak.
- Cache the expensive aggregate. A dashboard count that is fine a few seconds stale can be cached, so a hundred staff loads do not each run the same heavy query.
- Move slow work off the request. Reminders, report generation and syncs belong in a background job, not in the page the clinician is waiting on.
-- Build the index without locking the live table.
CREATE INDEX CONCURRENTLY idx_appt_clinician_day
ON appointments (clinician_id, starts_at);
Buy, build or hire the fix?
| Option | Choose this when | Trade-off |
|---|---|---|
| Scale the server or database tier up | You need breathing room today and the bill is acceptable | Hides the real cause; a missing index stays missing and costs grow |
| A no-code or vendor platform | You have outgrown spreadsheets and do not want to run infrastructure | You cannot tune the queries; peak-load behaviour is the vendor's to fix |
| Fix it yourself with EXPLAIN and logs | A developer can read query plans and owns the codebase | Lowest cost; needs someone who can tell a plan problem from a pool problem |
| A code rescue or performance pass | You want it diagnosed and fixed with tests so it stays fixed | An outside dependency; scope the regression tests into the work |
Doing it yourself is realistic: plan 4 to 8 hours if you can read a query plan and run migrations safely. The main risk of going alone is adding an index that does not match the query, or caching data that must always be fresh, such as a current medication list.
Why RAITHub for this
- Database performance is everyday work. PropDesk, a property-management SaaS RAITHub built, runs 1,024 tests across 130+ endpoints with 5 daily automation jobs that keep slow work off the request path.
- Fixes land behind tests. Each change ships with a regression test, so the slow path does not come back on the next release, as covered in every release breaks something.
- Honest limits. This is performance engineering, not a compliance exercise. RAITHub has built a healthcare scheduling app for a client but has not shipped a regulated health product, and it holds no SOC 2 or ISO 27001 certification. Production and patient data stay in your own covered cloud; development uses synthetic data.
When you don't need us
- The slowness is one missing index your own developer can add in an afternoon.
- You are pre-launch with a handful of users; profile first, optimise when real load appears.
- You already have an engineer who reads query plans and owns performance budgets.
How RAITHub would fix this
- Scope: a free 15-minute call on the symptoms, the peak pattern and the stack; you confirm you are authorised to have the app diagnosed.
- Diagnose: add a health-check route, enable slow-query logging, and rank the hot paths by their cost under real concurrency.
- Fix: indexes, N+1 removals, pooling and caching, applied one at a time against synthetic data and retested under load.
- Deliver: the fixes behind regression and load tests in your repository, with a short runbook for the next spike.
- What you receive: tested fixes, CI gates, a handover note, and full IP; an NDA is standard. Timeline for a focused performance pass is typically within a 2 to 4 week code rescue.
The work is fixed-price, quoted in writing after the call. See SaaS development, the HealthTech industry page, and the sibling guide on scaling from one clinic to many. To book it, talk to RAITHub about your HealthTech app.
General engineering information only; confirm compliance obligations with your adviser. Documentation checked on 11 October 2026.
Frequently asked questions
Why is my healthcare app slow only during clinic hours?
Because peak hours add concurrency and data volume at once. Many staff and the patient portal hit the same tables together, so a query that is fast alone queues behind others on a slow index scan or waits for a free database connection. Overnight the same query runs against a small, uncontested working set and feels fine.
How do I know if the problem is the database or the server?
Add a health-check route that reports database round-trip latency and a sample query time. If the server CPU is idle while requests are slow, the time is spent waiting on the database or the connection pool, not computing. Then run EXPLAIN ANALYZE on the slow query to see whether it scans a table that should be indexed.
What is an N+1 query and why does it slow a patient page?
An N+1 pattern runs one query to load a list, then one more query per item to load its related data, so a page with fifty appointments fires fifty-one queries. Under load those multiply across users. Loading the related rows in a single join or include collapses it to two queries.
Will adding more server capacity fix it?
Usually only for a while. Scaling up hides a missing index or an N+1 pattern behind raw power, so the bill grows while the real cause stays. Fix the query path first; scale when the work itself genuinely needs more capacity.
Is it safe to tune a live healthcare database?
Build indexes concurrently so they do not lock the live table, test each change against synthetic data first, and keep production and patient data in your own covered cloud. Treat performance changes like any other release: behind a test, with a rollback plan. This is engineering guidance; confirm data-handling obligations with your adviser.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.