← All articles

A Python runtime for edge functions

Ines Okafor · 2 min read

It has been hard to ignore how much Python now runs in front of users. Teams who used to keep it behind an internal API are putting it straight onto the request path — scoring a recommendation, validating a webhook, rewriting a prompt before it ever reaches a model.

Until this week, that meant paying a cold-start tax measured in seconds. Today we're shipping the Kestrel Python runtime for edge functions, built around snapshot restore. Install the SDK with pip install kestrel, set KESTREL_TOKEN, and try the complete example below:

app.py
import asyncioimport kestrelfrom kestrel.edge import route, Response@route("/score")async def score(request):    body = await request.json()    model = kestrel.models.get("ranker-small")    result = await model.predict(        features=body["features"],        timeout_ms=40,    )    return Response.json({"score": result.value})if __name__ == "__main__":    asyncio.run(kestrel.serve())

Why cold starts hurt more in Python

A Node function starts in tens of milliseconds because most of what it needs is already compiled into the engine. A Python function imports. Every import numpy walks the file system, compiles bytecode and allocates objects before your handler has seen a byte of the request.

Cold-start latency after snapshot restore
Median and tail cold-start latency across 18 rollout days.

We measured the import phase across forty thousand production deployments. The median function spent 71% of its cold start importing modules it would need on every single request.

Snapshot, then restore

Instead of importing on every cold start, we import once — at build time — and freeze the interpreter's memory to disk.

  • The build runs your module top level, exactly as it would at boot

  • We pause the process and write a copy-on-write snapshot of its heap

  • On a cold start the edge maps that snapshot and resumes your handler

  • Anything that must be fresh per instance (random seeds, sockets) re-initialises through a @on_restore hook

hooks.py
from kestrel.edge import on_restore
import random, httpx

client = None

@on_restore
def reconnect():
    global client
    random.seed()
    client = httpx.AsyncClient(http2=True)

A snapshot is only as safe as the state you let into it. If it holds a secret or a socket, it holds it for every instance that restores it.

What it costs, and what it doesn't

Before

With snapshots

p50 cold start

1,840 ms

96 ms

p99 cold start

4,210 ms

310 ms

Build time

38 s

44 s

Image size

112 MB

131 MB

The trade is a few seconds of build and some extra megabytes of storage for every function — which is the right direction to move cost, because builds happen once and cold starts happen to your users.

Try it

Snapshot restore is on by default for new Python projects on every plan. Existing projects can opt in with one line in kestrel.toml:

kestrel.toml
[functions.python]
snapshot = true

We'd love to hear what you build with it.

0 comments

  • Be the first to comment.