Supabase hitting 100% usage, overlimit, and connection lost errors with only 5 active users (Next.js App Router) #48678
Replies: 4 comments
|
You should be clear on what usage you are exceeding. I don't use next.js but you should also be able to look at the API Gateway logs to see if your caching is working. |
|
Building on @GaryAustin1's point about identifying what you're actually exceeding, there's a premise here worth killing first, because it changes what you should go looking at. There is no API request quotaThe pricing page lists "Unlimited API requests" on all four plans, Free included. So "100% API usage" is not a billable meter you can exhaust, and no amount of Whatever is at 100% is one of the metered items on Manage your usage. For an app with 5 users and the architecture you describe, the realistic candidates are Egress (Free includes 5 GB) and Compute or Disk IO. Your "50,000+ active users" figure is the Free plan's Monthly Active Users allowance, which counts distinct auth identities per month. It says nothing about throughput, so it was never the right yardstick for this. Gary's pointer to the API Gateway logs is the fastest way to settle it. Group by path: if Alongside that, in the SQL Editor: select calls, mean_exec_time, total_exec_time, query
from pg_stat_statements
order by calls desc
limit 20;Your N+1 profile lookups will sit at the top by 1.
|
|
Both answers above point at the number of requests — the timers, the N+1 loop, the middleware. That is the right place to start, but it assumes each request costs roughly the same. If the meter sitting at 100% turns out to be Compute or Disk IO rather than Egress, that assumption is where the explanation breaks, because with RLS enabled the cost per request is not constant. It scales with rows scanned. An RLS policy is a predicate Postgres evaluates per row it examines. So the N+1 loop is the multiplier and the policy is the amplifier, and they compound: 20 tasks in a loop is 20 requests, and each of those 20 runs your Four things make that per-row cost collapse. These are Supabase's own RLS performance recommendations, and the numbers are from the benchmark suite the docs cite — which, as it happens, is Gary's repo, so he can correct me if any of it has moved. 1. Wrap function calls in a -- re-evaluated for every row
using ( auth.uid() = user_id )
-- evaluated once per statement, as an InitPlan
using ( (select auth.uid()) = user_id )Benchmarked at 179 ms → 9 ms for plain 2. Index the columns your policies filter on. 3. Add 4. Avoid joining the source table inside the policy. Invert it so the subquery filters on You can check all of this without reading a single policy. In the dashboard: Advisors → Performance. Lint To confirm it is really RLS and not just query volume, extend the select calls, mean_exec_time, total_exec_time, shared_blks_read, query
from pg_stat_statements
order by total_exec_time desc
limit 20;Sort by And to see the policy in the plan, you have to ask as a role that is actually subject to it — as begin;
set local role authenticated;
set local request.jwt.claims = '{"sub":"<a real user uuid>"}';
explain (analyze, buffers) select * from tasks limit 20;
rollback;If the policy appears as a None of this makes the timers or the N+1 loop fine — fix those regardless, and |
|
I should say up front that I'm not certain this will fix it. I haven't been able to test the toggle on a project that has the beta, so treat the steps above as a way to narrow the problem down rather than a guaranteed fix. There's also one thing missing from your report that would help a lot, and that's the response bodies. You gave the status codes, 400 and then 500, but not what came back with them. Studio won't show you that either. The form surfaces error?.message in a toast and nothing more, so whatever the API actually returned never reaches the screen. The network tab is the only place you'll find it. A 400 on a config write normally names the field it turned down, and that alone would probably point straight at the cause. Without it we're all guessing, me included. A few other things might be involved, though I'd call these hunches rather than conclusions. The project is new, so it may have been created in a window where the OAuth server settings exist in the API but nothing sets up the record behind them. The 400 first and 500 after is also what you'd see if the first write was rejected cleanly and the retries then hit something half-written, which would explain why trying again made it worse instead of the same. And it's worth mentioning sa-east-1 and Auth v2.195.0 when you report it, since a gradual rollout by region or by Auth version would look just like a bug from where you're standing. |
Uh oh!
There was an error while loading. Please reload this page.
I am building a CRM application using Next.js 14 (App Router) and Supabase (SSR). Currently, we only have about 5 active users testing the application, but we are constantly hitting Supabase usage limits.
We are seeing the following errors in our logs:
Overlimit / 100% API usage
Connection lost / timeout errors
Supabase advertises supporting 50,000+ active users, so I am trying to figure out if there is an architectural flaw in my code that is causing an explosion of database requests.
Here is a breakdown of our current architecture and data-fetching strategy:
Next.js Middleware We have a middleware.ts file that protects our routes. It currently uses supabase.auth.getSession() on every request to verify the user's session before allowing access to the dashboard.
Background Polling (React Client Components) We have a few client components that use setInterval to keep data fresh while the user leaves the tab open:
A meeting reminder component that fetches the session, user profile, and upcoming meetings every 60 seconds.
A follow-up reminder component that fetches data every 5 minutes.
An auto-refresh component that triggers router.refresh() every 5 minutes, forcing the server components on the current page to re-fetch their data.
3. Supabase Realtime We have 4 different components establishing supabase.channel() connections to listen for live updates (notifications, leads, and leave approvals).
My Questions:
Is calling getSession() in the middleware on every request (including static assets or API routes) enough to blow through the API limits for 5 users? Should I be using getUser() or handling this differently in the App Router?

How heavy is the penalty for using router.refresh() on an interval? Does this completely bypass the Next.js cache and hit the Supabase database directly every time?
Are the connection timeouts likely caused by the Realtime websockets, or by connection pool exhaustion from the background setInterval polling?
Any advice on which of these patterns is the biggest bottleneck and how to refactor them would be greatly appreciated!
All reactions