Supabase Free Plan Limits: How to Survive Disk & CPU Exceeded
"Shut Down the Service in One Week?"
I launched a toy project, and the community response was great. Watching users grow from 100 to 500 felt amazing.
But one morning, a terrifying email arrived from Supabase.
"Your project has exceeded its database disk size limit. Changing your project to read-only."
My DB exceeded 500MB and entered Read-only mode. Users complained, "Cannot login," "Cannot post." The service effectively shut down. "Wait, 500MB for just text storage? No way."
What Confused Me Initially? (Weight of Text)
I only stored email/names in users and some articles in posts.
Text data is usually KB sized. 500MB seemed impossible.
"Is the database lying? Are logs piling up?"
I analyzed Storage Usage and found the culprits:
- Index: Indexes often outweigh the actual data.
- Toast Table: Compressed long text (like blog bodies) lives here.
- WAL (Write Ahead Log): Transaction logs accumulated over days.
Crucially, I didn't know "Deleting data doesn't immediately free up disk space."
The 'Aha!' Moment (Moving Boxes Analogy)
I understood it as "Moving Boxes."
- INSERT: Packing items into boxes.
- DELETE: Taking items out. But the Box (Disk Space) remains. An empty box still occupy warehouse space.
- VACUUM FULL: Crushing empty boxes and repacking items tightly into new boxes. Only this frees up space.
I was deleting items but leaving the empty boxes cluttering the warehouse.
The Fix: Surviving Free Tier
Supabase Free Tier (500MB) is small but lasts long if optimized.
Step 1: Find the Culprit (Analysis Query)
Run this in SQL Editor to see who eats space.
SELECT
relname as Table,
pg_size_pretty(pg_total_relation_size(relid)) As Size,
pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) as External_Size
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;
For me, it was a logs table I created manually.
"Ah, I dumped all console.log into the DB..."
Step 2: Run Vacuum
After massive DELETE, run VACUUM.
-- Light cleaning (No downtime, less space reclaimed)
VACUUM users;
-- Deep cleaning (Locks table, Reclaims all space)
VACUUM FULL users;
⚠️ Warning: VACUUM FULL locks the table. Do it during off-peak hours.
Step 3: Index Diet
"Just in case" indexes consume huge disk space. Drop unused indexes.
DROP INDEX IF EXISTS idx_users_last_login;
Step 4: Disable Point-in-Time Recovery (PITR)
PITR generates massive WAL logs. For free tier projects, standard daily backups might be enough. Check Project Settings -> Database -> Backups.
Deep Dive: CPU Limits
Disk isn't the only issue. CPU (Instance Size) is strict. Micro instance (Free) has 2 CPUs, 1GB RAM. A slight traffic spike or bad query sends CPU to 100%.
The main cause is "N+1 Query". Using ORM (Prisma) incorrectly can fire 100 queries for 1 user visit. Check Supabase Dashboard -> Query Performance. Optimize the "Most Time Consuming" queries. If you don't fix this, upgrading to the $25 Pro plan won't save you; it'll just crash later.
Application: Log Diet
Many devs store "Request Logs" in a SQL table. RDBMS is not for logs. It's a waste of expensive SSD. Use Supabase Log Drains or external services (Sentry, LogFlare).
7. Deep Dive: Connection Pooling (Supavisor)
Serverless functions (Vercel) spin up and down in milliseconds. If each function creates a new DB connection, the DB CPU explodes just simply handling the "Handshakes". You get "Too many clients" error.
Use Supavisor (Port 6543)
Supabase provides Port 6543 (Transaction Mode) and 5432 (Session Mode). For Serverless, ALWAYS use 6543.
# .env
# ❌ Session Mode (5432) - Will crash in serverless
DATABASE_URL="postgres://postgres:pw@db.bit.supabase.co:5432/postgres"
# ✅ Transaction Mode (6543) - Reuses connections efficiently
DATABASE_URL="postgres://postgres:pw@db.bit.supabase.co:6543/postgres"
If you miss this, Prisma will eat up all connection slots instantly.
8. Case Study: The Reddit Hug of Death
A toy project getting featured on Reddit or Hacker News can send traffic 100x higher in minutes. This phenomenon is common enough to have a name: the Reddit Hug of Death. On the Free Tier, this scenario plays out predictably.
The Typical Crisis
- Concurrent users: 10 -> 3,000.
- DB CPU: 100% frozen.
- Error: "Connection Timed Out".
Here's what to do when it happens.
Emergency Fix (Firefighting Mode)
- Read Replica? (No): Not available on Free Tier.
- Caching (Yes): The fastest query is NO query.
- Enable Next.js
revalidate: 60(ISR). Serve cached HTML for 1 minute intervals without hitting the DB at all.
- Enable Next.js
- Disable Realtime: Broadcasting to 3,000 sockets eats CPU alive. Turn it off temporarily.
The Outcome
CPU drops back to 20%. "If the DB is dying, stop hitting it. Cache at the Edge." That's the principle that matters most under sudden high traffic.
9. FAQ: When to Pay?
Q: When should I upgrade to Pro ($25)? A:
- When Disk 500MB is full of actual data (after cleaning logs/indexes).
- When data becomes business-critical and you need PITR/Daily Backups.
- When you need better CPU/RAM performance (No Cold Starts).
Q: Does file storage count towards the 500MB?
10. Deep Dive: Storage Optimization (WebP & Client-Resizing)
Storage limit is 1GB. If users upload raw 10MB photos, you hit the limit in 100 uploads.
The Fix:
Supabase Pro offers Image Transformation.
For Free Tier: "Resize on Client."
Use browser-image-compression or Flutter's flutter_image_compress.
Shrink 10MB -> 200KB before it even leaves the user's device.
11. Case Study: The Realtime Trap (200 Concurrent Limit)
The deadliest limit in Free Tier is 200 Concurrent Realtime Connections. The 201st user gets blocked.
The Fix: Fallback to Polling Don't use Realtime for everything.
- Notifications: Use FCM (Push).
- Feeds: Use Pull-to-Refresh.
- Chat: Use Realtime here, but implement a "Fallback Mode". If Realtime connect fails, switch to Polling every 3 seconds.
12. Architecture: Secret of Supavisor (PGBouncer)
"Connection Limit" is the most confusing part of Free Tier. Supabase uses Supavisor, a scalable connection pooler.
- Direct Connection (5432): 1 Client = 1 Connection. (Max 60).
- Transaction/Session Pooler (6543): Accepts thousands of clients, multiplexes them into few DB connections.
If you use Serverless, this is Mandatory.
Check your Prisma/Drizzle config for ?pgbouncer=true.
This single change fixes 90% of "CPU 100%" issues.
13. One-Line Summary
One-Line Summary
Free 500MB is bigger than you think. If full: 1. Delete useless indexes, 2. VACUUM after delete, 3. Move logs out. Upgrade only after trying these.