DEV Community

alex
alex

Posted on

taming cold starts in supabase edge functions with ts & pg

when a Next.js app offloads auth and webhook handling to Supabase Edge Functions, the first request after a period of inactivity can spike latency past 2 seconds – a noticeable jank for realtime dashboards.

why the pause happens

edge functions run on isolated V8 isolates that are torn down after a configurable idle timeout (default ~5 minutes). on warm invocations the isolate reuses any in‑process state, but a cold start forces a fresh container, npm install, and most importantly a new pg client connection. the connection handshake alone can add 500‑800 ms, especially when TLS is involved.

keep‑alive vs pool inside the isolate

unlike traditional server processes you cannot keep a long‑living connection pool across isolates. the usual pg.Pool will still open a new socket on each cold start, then close it when the isolate dies. the trick is to lazily create a single client and reuse it only while the isolate lives.

import { createClient } from '@supabase/supabase-js'
import { Client } from 'pg'

let pgClient: Client | null = null

export const handler = async (event: any) => {
  if (!pgClient) {
    // first call in this isolate – establish the connection once
    pgClient = new Client({
      connectionString: process.env.SUPABASE_DB_URL,
      ssl: { rejectUnauthorized: false }
    })
    await pgClient.connect()
  }

  const { data } = await pgClient.query('SELECT now()')
  return new Response(JSON.stringify({ now: data[0].now }))
}
Enter fullscreen mode Exit fullscreen mode

the pgClient lives in module scope, so subsequent invocations reuse the same socket until the isolate is reclaimed.

trade‑offs to watch

  • idle timeout – if your traffic pattern has >5 min gaps, every request still incurs the full cold start. you can tune EDGE_FUNCTIONS_TIMEOUT in Supabase dashboard, but longer lifetimes increase resource consumption and cost.
  • connection limits – each warm isolate holds an open DB connection. on a high‑scale deployment you could exhaust the Postgres max‑connections count. a pragmatic guard is to cap concurrent edge functions with Supabase quotas or implement a tiny “connection broker” that proxies through a lightweight pooler like PgBouncer.
  • error handling – when an isolate is reclaimed the client is abruptly closed, causing ECONNRESET on in‑flight queries. wrap queries in a retry block that re‑creates the client if pgClient is null or the query throws a connection error.
  • TLS handshake cost – you can shave a few hundred ms by using Supabase’s native pg connection string that enables sslmode=require without full certificate validation, but be aware of security posture in production.

monitoring cold starts

Supabase logs expose X-Edge-Function-Invocation headers with cold-start:true. ship a tiny middleware that records the latency delta and feeds it to your observability stack – you’ll quickly see whether your traffic pattern justifies a higher idle timeout or a fallback to a traditional serverless function.

anyone else juggling edge isolates with Postgres? how do you balance connection churn vs latency in your own stack

Top comments (0)