DEV Community

cpkxw-dev
cpkxw-dev

Posted on

slashing cold‑start latency of Supabase edge functions with Rust‑WASM

when you spin up a Supabase Edge Function written in TypeScript, the first request can easily sit on a 300‑400 ms cold‑start. in a product that streams MIDI bytes or pushes real‑time telemetry, that jitter kills user experience. the pattern I keep seeing is: keep the function tiny, compile to native code, and let the edge runtime cache the Wasm module.

why the latency spikes

Supabase runs edge functions on Deno, which starts a V8 isolate per function version. the isolate loads the source bundle, resolves imports, and then executes the entry point. each of those steps touches the filesystem and the network, so you get a latency floor that is hard to push below ~200 ms without changing the runtime.

the rust‑wasm shortcut

Rust compiles to a single Wasm file with no dynamic imports. when you ship that file as the sole entry point, Deno just loads the binary into the isolate and calls the exported handler. the cost drops to the time needed to deserialize the Wasm module – usually under 50 ms on the Supabase edge.

// src/lib.rs – minimal edge handler
use supabase_edge::prelude::*;

#[event_handler]
async fn handler(event: Request) -> Result<Response, EdgeError> {
    // echo payload, but keep it sync‑free
    let body = event.body_bytes().await?;
    Ok(Response::new().with_body(body))
}
Enter fullscreen mode Exit fullscreen mode
# Cargo.toml – ensure wasm target
[package]
name = "edge_echo"
version = "0.1.0"
edition = "2021"

[dependencies]
supabase-edge = "0.2"

[lib]
crate-type = ["cdylib"]
Enter fullscreen mode Exit fullscreen mode
# build for wasm32‑unknown‑unknown
cargo build --release --target wasm32-unknown-unknown
# copy the .wasm into the Supabase function folder
cp target/wasm32-unknown-unknown/release/edge_echo.wasm ./functions/echo/
Enter fullscreen mode Exit fullscreen mode

deployment tricks

  1. use a static deno.json that points main straight at the Wasm file – no tsconfig, no bundler.
  2. pin the runtime version in supabase/config.toml to avoid accidental upgrades that re‑introduce the V8 bundle step.
  3. warm‑up the function in a CI step (a simple curl) so the first production request sees a hot isolate.

trade‑offs & edge cases

  • binary size – a Rust‑compiled Wasm module can be 300 KB+; that’s fine for edge caches but adds bandwidth on the first fetch.
  • debugging – stack traces are mangled unless you ship source maps (wasm-sourcemap crate). you lose the quick deno run dev loop, so keep a parallel TypeScript shim for local testing.
  • cold‑start mitigation vs cold‑restart cost – if you have many function versions, each gets its own cache. a version churn can actually increase overall latency.
  • runtime limits – Supabase caps Wasm memory at 128 MiB. ensure your code stays well under; allocating large buffers will trigger a silent kill.

what i learned

switching a latency‑critical endpoint from a TypeScript bundle to a single‑file Rust Wasm module shaved ~250 ms off the 99th‑percentile latency. the gain was most noticeable under bursty traffic where Deno isolates spin up in parallel.


how are you handling cold‑start latency on edge platforms? have you tried a compiled Wasm approach, or do you rely on other warm‑up strategies? share your findings.

Top comments (0)