TL;DR: AI coding tools write queries that work fine with a handful of test rows. Two patterns cause almost every database slowdown after launch. The first is N+1 queries: one query per row, instead of one query total. The second is missing indexes on the columns your app filters by most. Fix both. Add connection pooling and short transactions too. Most database performance problems go away.
Your app worked fine in testing. Ten test rows, one test user, everything fast. Then real users show up, the table grows, and pages that used to load in a blink now take seconds. Nothing crashed. Nothing looks broken in the code. The database is just doing far more work than it needs to.
This happens to AI-built apps more than most, because the code that works with ten rows and the code that works with ten thousand rows look almost identical on the page. The difference only shows up under load. Want a quick read on your own app first? Run the free scan on your live URL.
N+1 queries: the most common performance finding we see
An N+1 query is simple to describe and easy to miss. You load a list of 50 rows, then run one more query for each row to get its details. That's 51 queries where a single join would do the same job in one.
AI coding tools write this pattern often. It's the most direct way to get a feature working: fetch the list, then loop over it and fetch what each item needs. With ten rows of test data, this runs in a blink. With a real user's data, it turns one page load into fifty or a hundred round trips to the database.
- Find it: turn on query logging for one busy page and count the queries per request. Ten or more near-identical queries on one page load is your signal.
- Fix it: replace the loop with a join, or batch the follow-up queries into one call using an "in" clause instead of one query per row.
- Watch for it again: a new feature that loops over a list and fetches related data is the most common place this pattern comes back.
Missing indexes: the fix AI tools skip by default
Every table gets a primary key index automatically. Nothing else is automatic. If your app filters or sorts by any other column, and there's no index on it, the database reads every row to answer that query.
For a multi-tenant app, the columns you filter by most are usually tenant ID, user ID, and created date. If those don't have an index, every query scoped to one tenant scans the whole table to find that tenant's rows.
- Start with your hottest queries. List the queries that run on every page load, not just the slow ones.
- Add composite indexes for combined filters. A query filtering by tenant ID and date needs one index covering both columns, not two separate indexes.
- Check the query plan. Run
EXPLAIN ANALYZEon your query. A sequential scan on a table with more than a few thousand rows means you're missing an index. - Re-check after every migration. A new index can change which query plan the database picks for other queries too.
Indexes and row-level security are linked too, more than most founders expect. If your RLS rule calls a function like auth.uid() the wrong way, the database can run that function once per row. It should only run once per query. This is a common mistake, and it is easy to fix. We cover it in our guide to Row Level Security performance.
Connection pooling: the outage you don't see coming
Every database connection has a cost. Every managed database caps how many it allows at once. Serverless functions make this worse. Each one can open its own connection. Add a traffic spike, and hundreds of connections can open in seconds.
Without pooling, one of two things happens under load. New connections get refused. Or the database runs out of memory holding them open. Either way, real users see errors that have nothing to do with your code.
- Turn on pooling. Most managed Postgres providers, including Supabase, ship a built-in pooler. Use it for anything serverless.
- Cap concurrency for expensive queries. A report or export endpoint should not open unlimited parallel connections.
- Separate background jobs from request traffic. A worker pool that shares a connection budget with your API can starve real users during a batch job.
Transaction boundaries: stop partial failures from sticking
A transaction that wraps too much work is slow. It holds locks longer than it needs to. A transaction that wraps too little work is risky. It can leave you with half-finished writes when something fails partway through.
- Keep transactions short. Lock only what needs to change together.
- Make retries safe. If an operation can be retried, design it so a retry never creates a duplicate write.
- Move side effects outside the transaction. Sending an email or calling another API inside a database transaction lets a slow third party hold your locks open.
| Symptom | Usual cause | First thing to check |
|---|---|---|
| List or dashboard page slows down as data grows | N+1 queries | Count queries per request on that page |
| One query is fine alone, slow at scale | Missing index | EXPLAIN ANALYZE the query |
| Random "too many connections" errors under load | No connection pooling | Check your provider's connection limit vs. peak usage |
| Partial or duplicate records after a failure | Transaction boundaries too wide or missing | Trace what happens if the request fails halfway through |
These four patterns sit in the Performance dimension of every scan we run. Security, Reliability, and Monitoring are the other three. Need the security side of database work too? Row-level security, storage buckets, backups? That is a separate guide: our database hardening checklist.
Find out which of these your app has
Free URL-based scan. No code access. About 30 seconds. Security, reliability, performance, and monitoring in one score.
Run the free scanWhat a full audit adds on top of the free scan
The free scan is $0. It gives you a Launch Readiness Score. The full Launch Readiness Audit is $499 one time. A senior security engineer reviews every finding. You get file references where they apply, plus a fix roadmap. Delivered within 48 hours. Ongoing monitoring starts at $149 a month.
FAQ
What is an N+1 query and why does AI-generated code create them?
An N+1 query is one query to get a list, then one more query for each row in that list. Load 50 users, then run 50 more queries to get each user's data. That is 51 queries where a single join would do. AI tools write this pattern often, because it is the simplest way to get a feature working, and it never gets flagged until real traffic hits it.
How do I find N+1 queries in my own app?
Turn on query logging for one busy page and count the queries per request. A details page or dashboard that fires ten or more near-identical queries is almost always an N+1 pattern. Most ORMs also have a built-in query counter for local development.
Do I need connection pooling for a small SaaS app?
Yes, well before you expect to. Every serverless function or background job that opens its own database connection adds up fast, and most managed databases cap total connections. Without pooling, a burst of traffic or a stuck background job can use up every connection and lock out real users. Most managed Postgres providers, including Supabase, ship a pooler you can turn on.
How do I know which columns need an index?
Start with any column you filter or sort by on a page that loads often, and any column used to scope one tenant's rows away from another's. Run EXPLAIN ANALYZE on that query. A sequential scan on a large table is your signal to add an index.
Is a database performance check worth it if my app already passed a security scan?
Yes, they check different things. A security scan checks who can reach your data. A performance check tests whether your database survives real traffic. Both are part of the same free Launch Readiness Score, but N+1 queries and missing indexes will not show up as security findings.
Research sources
- OWASP Foundation — OWASP Top 10 Web Application Security Risks (2021), referenced for how performance shortcuts can widen security gaps
- MITRE Corporation — CWE Top 25 Most Dangerous Software Weaknesses (2024)
- Jai Mittal, Founder & CTO, Launch Ready Code — Proprietary data from 700+ AI-built app audits, 2025–2026. Average Launch Readiness Score: 44/100. N+1 queries and missing indexes are among the most common Performance findings across those scans.