Troubleshooting guide

App times out or hits a quota limit at launch: how to fix it

The app worked in testing. Then real traffic arrived and a search timed out, a function hit its time limit, a free-tier quota ran out or the host paused the project. In our data of 125 verified builder reports, this is the largest single pattern: work that grows with the size of the data store instead of the request, plus limits nobody was watching.

125 verified cases15 min read Updated 7 Nov 2026Last reviewed 7 Nov 2026How we collect cases

What you'll see

  • A search or list endpoint takes seconds, then times out, once the table holds real data.
  • The first request after a quiet period returns a 500 or a 504, and the next one works.
  • A serverless function stops mid-job. Vercel, for example, terminates it when it runs past its maximum duration.
  • The host pauses or blocks the project after a free-tier or plan limit is reached, and deploys or requests fail until you act.
  • A webhook handler or background job fails silently, and a queue of retries builds up behind it.
  • The bill or the request count jumps overnight because a loop, a crawler or a model call keeps retrying.

Why it happens

Cause 1

A query whose cost grows with the table

A filter or sort on a column with no index forces a sequential scan, and a total count over a large table does the same. It is fast on 200 rows and slow on 2 million. Postgres shows this as a Seq Scan node in EXPLAIN output, and 'Rows Removed by Filter' shows how much work was thrown away.

Cause 2

Plan limits nobody monitors

Hosts cap function duration, daily requests, CPU time per request and project activity. On the Cloudflare Workers Free plan the documented limits are 100,000 requests a day and 10 ms of CPU time per request. Exceeding the daily limit returns error 1027, and exceeding CPU time returns error 1102. Supabase pauses Free plan projects with low activity over a 7-day period, and paid projects cannot be paused.

Cause 3

Calls with no timeout and retries with no ceiling

A slow upstream call with no timeout holds a worker until the platform kills it. Retries at several layers multiply. AWS's Builders' Library gives the example of five layers each retrying three times, which can raise load on a failing database 243 times. A model that keeps calling a tool, or a client that retries a failed page request, produces the same storm.

Cause 4

Long jobs with no resume point

A job that must finish inside one function invocation has nowhere to restart from when the limit hits. Work that failed halfway is either repeated from the start or lost, and compensation steps (refunds, notifications) never run.

Cause 5

Failures that leave no trace

Stripe retries an undelivered webhook for up to 3 days in live mode with exponential backoff, but only if your endpoint answers non-2xx. A handler that catches its own error and returns 200 stops the retries and hides the failure.

How to fix it

  1. Set an explicit time limit for each route

    Find the platform ceiling and set a lower limit on purpose, so a runaway call ends where you chose. On Vercel the defaults and maximums depend on plan and on Fluid compute, so read the duration table for your plan before you set it.

    // app/api/report/route.ts (Next.js App Router on Vercel)
    export const maxDuration = 30; // seconds; must not exceed your plan's maximum
  2. Index what you filter and sort on, and measure at production size

    Run EXPLAIN ANALYZE against a copy with production-sized data and look for Seq Scan on large tables. EXPLAIN ANALYZE executes the statement, so wrap anything that writes in a transaction and roll back.

    CREATE INDEX orders_user_created_idx ON orders (user_id, created_at DESC);
    EXPLAIN ANALYZE
    SELECT * FROM orders WHERE user_id = 42 ORDER BY created_at DESC LIMIT 25;
  3. Stop counting the whole table on every request

    Page results with a cursor or a limit. Show '10,000+' instead of an exact total, or keep the count in a small counter row that you update when records change. A count that runs before every query is a common cause of the search that times out.

  4. Put a timeout on every outbound call

    Base it on the downstream service's measured latency, not a guess. AbortSignal.timeout() aborts a fetch and throws a TimeoutError you can handle.

    try {
      const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });
    } catch (err) {
      if (err.name === 'TimeoutError') { /* fail fast, record it, move on */ }
    }
  5. Cap retries, back off with jitter and retry in one layer

    Choose a maximum attempt count and increase the wait after each attempt. Add jitter so clients that failed together do not retry together, and only retry idempotent operations. For queues, a dead-letter queue stops a poisoned message from looping. In Amazon SQS, the redrive policy's maxReceiveCount sets how many receives are allowed before the message moves to the DLQ.

    {
      "deadLetterTargetArn": "arn:aws:sqs:REGION:ACCOUNT:jobs-dlq",
      "maxReceiveCount": 5
    }
  6. Make long jobs resumable

    Split the work into steps and store a checkpoint (the last id processed) after each one, so the next invocation can pick up from it. Give every step an idempotency key so a replay does not charge or email twice.

  7. Alert on every quota before it reaches 100%

    List each limit on each plan you use (function duration, requests, CPU time, build minutes, email sends, project activity, billing status) and set an alert or a calendar check for each. Keep a valid payment method on file. Use an account email you can still receive codes at, because a billing pause can also block the sign-in code you need to fix it.

Check it's fixed

  • Load test the slow endpoint against a production-sized copy: EXPLAIN ANALYZE shows an index scan and the p95 response time sits well under your route's time limit.
  • Make an upstream dependency hang on purpose (a stub that sleeps): the request fails at your timeout, the failure is logged, and no retry storm follows.
  • Force a handler to fail: a webhook returns non-2xx, is retried, and the retry succeeds without duplicating a charge or an email.
  • Open your hosting, database and email dashboards: every quota shows an alert threshold below 100%, and nothing is within a week of a pause or trial end.

Fix it with Gemmein

On Gemmein, a relay keeps a row per event and retries a failed run on a ladder that ends in a dead run the owner is alerted about. Paid work on your own server can run as a pipeline with its own deadline, and relay events that wait longer than 30 seconds, or a Development app past its hourly limit, show on the Errors page.

  1. Let failed relay runs retry on a ladder that ends

    A failed run retries after 1 min, 5 min, 30 min, 2 h, 8 h and 16 h, then is dead; the owner is alerted and can replay it. For a relay your app calls (app_call), the charge and what it pays for run once while the caller waits, follow-ups after it retry on the relay ladder, and a replay of an app call's run is refused (409 not_replayable).

  2. Know what a retry or replay runs again

    Without an edit, a retry skips the steps that already succeeded and continues from the one that failed. After an edit, a retry runs the relay as it is now and a grant or take already done for that event is never repeated, but changing which product or access a grant gives counts as a new grant, and changing a plan or credit amount applies to new events only. A replay runs every step again. On deploys and restarts, unfinished runs go back to the queue and finished steps do not repeat, though a call_url that was in flight can arrive twice, so deduplicate on its id.

  3. Run paid work on your server as a pipeline

    A pipeline is a tool on provider external, not a relay: its bounds.deadlineMinutes (1..1440, default 60) is how long a run may take, and the runner posts the hand-off on its own retry ladder, where a non-2xx, timeout or no connection rides the ladder with the same body (dedupe on run.id) and a 4xx other than 408/429 is terminal (failed, hold released). g.pipelines.handle returns a Fetch-API handler that acks 200 { received: true }, runs fn in the background and posts its return to /complete or a thrown error's message to /fail; the helper cannot detect your host, so on Cloudflare Workers or Vercel you pass the host's keep-alive hook as options.waitUntil or fn's progress is lost silently, and for a long-lived Node/Bun/Deno server the docs list nothing to pass.

    export default g.pipelines.handle(process.env.PIPELINE_SECRET!, async (run, { progress }) => {
      await progress(50, "half way")
      return { text: `made: ${run.params.prompt}` }
    })
  4. Watch the Errors page for waits and limits

    In Development, past 120 immediate relay runs per app and 300 per account an hour, events wait their turn and run within minutes, never dropped. The Errors page lists events that waited longer than 30 seconds to run and a Development app that passed its hourly limit of immediate relay runs.

Questions

Why does the first request after a quiet period fail?

On a serverless host the first request can start a new instance, and a slow start or a paused database can push it past a timeout. Keep start-up work small and set explicit timeouts. Also check whether your database tier pauses on inactivity: Supabase pauses Free plan projects with low activity over a 7-day period.


Will upgrading the plan fix a function timeout?

Sometimes. Plans raise the maximum duration, but a query that scans a whole table still gets slower as data grows. Fix the query and set a deliberate limit, then upgrade only if the job needs the longer ceiling.


How many times should I retry a failed call?

Few, at one layer, with backoff and jitter, and only for idempotent operations. AWS's guidance is to cap attempts and handle failures earlier in the architecture instead of retrying at every layer.


My project was paused. Is the data gone?

Not necessarily. Supabase documents that a paused Free plan project can be restored from the dashboard for a limited period, so check your project page and restore it before that window closes. Other hosts differ, so read their pause or suspension policy.


Why did a failed payment take my site offline?

Many hosts suspend or block deploys when a payment method fails or a trial ends. Add a spare payment method and send billing emails to an inbox you can reach. Monitor the billing status the way you monitor any other quota.


Sources

Last reviewed 7 Nov 2026. What changed: 1 line(s) reworded. Checked against: vercel.com, supabase.com, postgresql.org, builder.aws.com, docs.aws.amazon.com, docs.stripe.com, developers.cloudflare.com, developer.mozilla.org. How we check this

← Back to the full report