
How to Fix Slow Supabase Queries & Optimize PostgreSQL Performance
A developer's guide on how to fix slow Supabase queries, resolve connection exhaustion limits, and optimize PostgreSQL performance using indexes, Supavisor, and RLS tuning.
Supabase Performance: Hard-Earned Lessons in Optimizing Your Database and API
Supabase is a joy to use, no doubt about it. But here's the harsh truth: when your app starts getting real traffic, those "naive" queries you wrote during the MVP phase will come back to bite you. Slow page loads, timeouts, or worse—taking down your entire app.
In this guide, I'm skipping the theoretical textbook stuff. These are the real-world lessons (and a few battle scars) I've picked up while optimizing Supabase across the database, API, and Edge Functions.
1. The Nightmare of Missing Indexes
Trust me, 90% of your slow queries are because you forgot to add an index, or put it in the wrong place. Postgres (the engine under Supabase's hood) is incredibly powerful, but if you force it to do a sequential scan on a massive table, it's going to cry.
Battle-tested tips:
- Stop guessing: Open the Index Advisor in your Supabase Dashboard (Database > Query Performance) immediately. It literally points out exactly which queries are choking because of missing indexes.
- Foreign Keys are mandatory: If you have an
orderstable pointing tousers, please index theuser_id. Otherwise, everyJOINwill scan the entire table.CREATE INDEX idx_orders_user_id ON orders(user_id); - Partial Indexes - The secret weapon: If you frequently query for online users, don't index the entire status column. Use a Partial Index—it's lightweight and lightning fast:
CREATE INDEX idx_active_users ON users(status) WHERE status = 'active';
2. Stop Abusing PostgREST
Supabase auto-generates a slick API (PostgREST) for us. It's incredibly convenient, but shockingly easy to abuse.
Kick the select('*') habit
When you're building a quick demo, sure, whatever. But in production, calling select('*') is like downloading an entire encyclopedia just to read the table of contents.
// ❌ Re-evaluate your life choices
const { data } = await supabase.from('users').select('*');
// ✅ This is the way
const { data } = await supabase.from('users').select('id, name, avatar_url');
No Pagination? You're asking for trouble
Are you absolutely certain that table will only ever have 10 rows? If not, always append .range() or .limit(). No exceptions.
3. RLS (Row Level Security): A Double-Edged Sword
RLS is arguably Supabase's killer feature. It saves us from writing a mountain of authorization logic in the backend. But the downside? RLS is evaluated on EVERY SINGLE ROW returned.
If you write a policy with complex subqueries or joins, your query speed will absolutely tank.
-- ❌ Your users will be waiting forever
CREATE POLICY "View same department data" ON data
FOR SELECT USING (
department_id IN (SELECT department_id FROM users WHERE id = auth.uid())
);
The Fix:
- Try to design your tables so RLS checks are as simple as possible (e.g., relying on JWT claims).
- If the logic is unavoidably complex, consider using Security Definer Functions (writing a Postgres function that runs with admin privileges to fetch data, bypassing RLS where appropriate). But use this very carefully!
4. The Serverless "Connection Exhaustion" Panic
If you're using Next.js API routes or Cloudflare Workers to hit Postgres directly, you will inevitably see that terrifying error: connection exhausted.
Each Postgres connection eats up about 10MB of RAM. Serverless environments scale aggressively, opening hundreds of connections and leaving them hanging.
The Solution: Turn on Supavisor. Supabase has a built-in connection pooler called Supavisor.
- Running serverless (Prisma, Drizzle)? Change the port in your connection string to
6543right now. It pools the connections and lets your database breathe.
5. Don't Force Your Database to Do Everything
I've seen developers use Postgres functions to make external API calls (via pg_net). Just don't.
Your database should stick to storing and retrieving data. Heavy computational tasks, third-party API calls, sending emails... push all of that to Supabase Edge Functions. They run fast, have minimal cold starts, and most importantly, they don't drag down your primary database performance.
6. Final thought: "The fastest query is the one you don't make"
No matter how well you optimize, querying the DB still takes time. If the data rarely changes, cache it. If you're using Next.js, lean on Data Cache or ISR. If the data updates constantly (like a live leaderboard), throw Redis (Upstash) in front of Supabase.
Optimization is a continuous loop. Get it working first, keep an eye on your Dashboard, and squash the bottlenecks as they appear. Happy optimizing, and may your DB never crash!
Related
Resources

The Best AI Tools for Developers in 2026

How MCP Works: A Complete Guide to Model Context Protocol

TypeScript 7.0: Inside the 10x Faster Native Go Compiler

Mastering AI-Assisted Coding: Practical Workflows for 2026
