NoCodeLab.ai

ResourcesDatabase Debt

Your AI-built app is fast today.

Here's why it won't stay that way.

AI coding tools ship features that work. What they quietly skip is the layer that keeps them working at scale: database indexes, efficient security policies, schema hygiene. We audited a real production database built with AI tools and found 859 performance findings. This guide explains why that happens to almost every vibe-coded app, and gives you the 15-minute audit that catches it.

BySara SimeoneFounder, NoCodeLab

One real production database, built largely with AI tools. Audited July 2026.

859
Performance findings
57
Foreign keys with no index
91
Indexes never used once

Nothing failed. That's the problem.

Four structural reasons AI coding platforms skip the performance layer. None of them are bugs, and none of them are going away soon.

Indexes never fail a test

An index changes how fast a query runs, never whether it works. AI platforms optimise for the visible acceptance test: the feature renders, the data saves, the demo works. A missing index throws no error and fails no test, so nothing in the generation loop ever pushes the model to add one.

The cost arrives after the session ends

With fifty rows, Postgres scans an entire table in microseconds, and a small table scan is genuinely faster than an index. Your app really is fast on launch day. The cost appears months later at fifty thousand rows, long after the AI session that created the table has ended. The platform never sees the consequence, so it never learns.

Postgres masks the gap

Primary keys and unique constraints get indexes automatically, so the lookups every demo exercises are fast for free. What Postgres does not auto-index is the thing most people assume it does: foreign keys. Every user_id and campaign_id linking your tables together is unindexed unless someone deliberately indexes it.

Security prompts multiply the cost

On Supabase, AI tools are pushed hard to generate Row Level Security policies, and they do, constantly. But a policy is a WHERE clause that runs on every query, and the AI writes it in the form that re-evaluates per row, on columns it never indexed. The security layer becomes the performance problem.

The five failure patterns

Every finding in our 859-item audit fell into one of five buckets. Check your own database against each.

1. Unindexed foreign keys

The headline pattern: 57 instances across 40+ tables. Any column ending in _id that references another table needs a covering index, or every join and lookup on it scans the table. The fix is one line, safe to run on production, and takes effect immediately.

The fix
create index if not exists orders_user_id_idx
  on public.orders (user_id);

2. Per-row security policies

89 policies re-evaluated auth functions for every row touched. The difference is one pair of brackets: wrapping the call in a select turns it into a value Postgres computes once per query instead of once per row. Identical security, a fraction of the work.

Before → after
-- AI writes this (re-evaluated per row):
using (auth.uid() = user_id)

-- Write this instead (evaluated once per query):
using ((select auth.uid()) = user_id)

3. Policy sprawl

617 findings, the biggest bucket. Each AI session adds another policy instead of consolidating the existing ones, and every overlapping permissive policy on the same table and action is evaluated on every query. Some overlap is legitimate (an admin policy alongside a user policy); ten policies accreted across ten prompting sessions is debt.

4. Indexes nobody asked for

The mirror image: 91 indexes that had never served a single query. When AI tools do add indexes, they guess, and every wrong guess taxes each insert and update while helping no read. The lesson isn't "add more indexes"; it's that indexing decisions need to come from how the data is actually queried.

5. Tables with no primary key

Two tables in our audit had no primary key at all: no way to uniquely address a row, no free index, and replication tooling that quietly refuses to work. Rare, but worth the ten-second check, because everything else assumes it.

The 15-minute audit

You don't need to be a DBA (database administrator, the person who normally tends this stuff). If your app runs on Supabase, the tooling is already in your dashboard.

  1. 1

    Run the Performance advisor

    Supabase Dashboard → Advisors → Performance. It reads your schema (not your data) and flags every pattern above, each with a remediation link. This is the exact tool that produced our 859-finding audit.

  2. 2

    Fix the foreign keys first

    Best effort-to-impact ratio in the list. Each one is a single CREATE INDEX statement, safe on production. Paste the advisor's findings into your AI tool and ask it to generate the statements. This is a task AI does reliably when told exactly what to do.

  3. 3

    Rewrite the per-row policies

    For every policy the advisor flags, wrap the auth call in a select as shown above. The meaning of the policy does not change, only the query plan does. Re-run the advisor afterwards to confirm the findings clear.

  4. 4

    Leave the "unused index" list alone, for now

    Usage stats are history, not prophecy: a new index shows as unused, and an index serving a rare-but-critical job (a monthly export, a reminder cron) can look dead. Review these quarterly with knowledge of your workload, not on day one.

Make the AI do it right from the start

The platforms won't volunteer this, but they follow instructions well. Put this in your project's standing instructions: Lovable's Knowledge, Cursor's rules, or your CLAUDE.md.

Copy into your project instructions
Whenever you create or modify database tables:

1. Add an index on every foreign key column.
2. Add indexes for columns used in WHERE, ORDER BY, or JOIN.
3. In Supabase RLS policies, write (select auth.uid()),
   never a bare auth.uid().
4. Prefer one permissive policy per role and action;
   consolidate instead of adding overlapping policies.
5. Every table gets a primary key.

After any schema change, run the Supabase performance
advisor and fix what it flags.

Five rules and a habit. They cost nothing at build time, and they are the difference between an app that scales quietly and one that mysteriously "gets slow" eight months in, usually the week it starts mattering.

Questions we hear

Why is my AI-built app getting slow?

Usually the database. AI coding platforms generate schemas that work perfectly at demo scale but skip the performance layer: indexes on foreign keys, efficient security policies, and query-aware design. With a few hundred rows nothing shows. As real usage accumulates, every query does more work than it should, and pages that loaded instantly start taking seconds.

Do AI coding tools create database indexes?

Rarely, and unreliably. Nothing fails without an index. The feature works, the demo passes, so nothing in the generation loop pushes the AI to add one. When we audited a production database built largely by AI tools, we found 57 foreign keys with no index and, at the same time, 91 indexes that existed but were never used. The tools both under-index where it matters and over-index where it doesn't.

What is an unindexed foreign key and why does it matter?

A foreign key is a column that links one table to another, like user_id on an orders table. Postgres does not index these automatically; only primary keys and unique constraints get indexes for free. Without an index, every join, every lookup by that column, and every cascading delete has to scan the whole table. It is the single most common performance gap in AI-generated schemas.

What is a Row Level Security policy?

Row Level Security (RLS) is how Postgres (and Supabase) decides which rows a given user is allowed to see or change. A policy is a rule attached to a table, written as a condition: for example, user_id = auth.uid() means "a user can only touch rows that belong to them." Postgres checks that condition on every read and write, so the rule enforces itself at the database level rather than relying on your app code to remember. It is the main tool that stops one user's data leaking to another, which is why AI tools generate them heavily, and why writing them efficiently matters.

Is Row Level Security slow in Supabase?

It doesn't have to be, but AI-generated policies often are. A policy written as auth.uid() = user_id re-evaluates the auth function for every row the query touches. Wrapping it as (select auth.uid()) = user_id makes Postgres evaluate it once per query instead. Same security, dramatically less work at scale. This is the number-one finding Supabase's own performance advisor raises.

How do I check my own database?

If you are on Supabase: open your project dashboard, go to Advisors, and run the Performance advisor. It flags unindexed foreign keys, per-row policy evaluation, duplicate indexes and more, with a remediation link for each. It takes about two minutes and reads your schema, not your data. Then fix the top category, usually a handful of CREATE INDEX statements.

Three doors. Pick the one that fits where you are.

Ready to put this to work? Pick where you start.

Lab Live

A free masterclass every first Thursday at 12:30pm UK. Sixty minutes. Sara demos one thing she has built that month, walks through how it works, and answers questions. No slides. No selling.

Register, free

The Soloist

8 sessions, built entirely around your role and your tools. By session three you will be saving a day a week. By session eight you have built one working system and know how to build the next.

Explore The Soloist

The Studio

6 months, your whole team. We turn what your business knows into AI powered IP your competitors cannot replicate.

Explore The Studio
Book a free strategy call

30 minutes. No pitch. We will tell you honestly which door is right, or if the answer is none of them yet.