Insights and Resources

Stateful Edge Functions

Stateful edge functions give edge compute persistent state without database round trips. Learn what they are, how they work, and why SQL at the edge matters.

Split panel showing stateless edge with 150ms latency vs stateful edge with zero-hop access

Takeaways

  • Stateful edge functions are edge functions that read and write persistent data without a round trip to a centralized database, because the state lives inside the same network as the compute.
  • Edge functions are stateless by default. Their runtimes block persistent TCP connections, and each invocation starts with no memory of the last one.
  • The cost of statelessness is geography. A function in Tokyo talking to a database in Virginia pays a physical latency floor that no amount of code optimization removes.
  • Two patterns solve it. A shared SQL database bound to many functions handles application data. A per-entity actor with embedded SQLite handles state that needs coordination.
  • Telnyx Edge Compute exposes both through zero-credential bindings, so there are no connection strings or API keys in your function code.

What are stateful edge functions?

A stateful edge function is an edge function that can read and write persistent data without a network round trip to a centralized database, because storage runs inside the same edge network as the compute. State survives between invocations, and the function reaches it through a runtime binding rather than a remote connection.

That is the opposite of how edge functions normally behave. A stateless edge function treats every invocation as independent. It has no memory of prior requests, holds nothing between calls, and fetches anything it needs from a database somewhere else. The compute is close to the user. The data is not.

Stateless vs stateful

Stateful edge functions close that gap. Instead of shipping a query across an ocean, the function talks to storage that lives in the same infrastructure it runs on. The result is persistent state, transactional reads and writes, and a latency budget that reflects local access rather than intercontinental fiber.

The statelessness problem

Edge platforms did not choose statelessness by accident. It falls out of the runtime model they picked, and it has consequences that show up the moment your function needs data.

Why edge functions are stateless by default

Most edge runtimes run JavaScript in V8 isolates rather than containers or virtual machines. Isolates start fast and cost almost nothing to keep around, which is what makes it practical to run your code in hundreds of locations. The tradeoff is that an isolate is not a long-lived process with its own network stack.

Vercel documents the consequence plainly. Its edge runtime does not support TCP or UDP connections, which rules out the standard Postgres and MySQL drivers every backend developer already knows. Cloudflare Workers, Deno Deploy, and Supabase Edge Functions land in the same place. You reach your database over HTTP, or you do not reach it at all.

Isolate vs server

Connection pooling goes with it. A traditional server opens a pool once and reuses it for thousands of requests. An edge invocation cannot hold a socket open for the next one, so every request pays setup cost again.

The edge latency trap

Here is the shape of the problem in practice. Your function runs in Tokyo in about 5ms. Your Postgres instance runs in us-east-1. Tokyo to Northern Virginia is roughly 10,900 km great circle. Light moves through fiber at about two thirds of its vacuum speed, so the one-way floor is around 55ms and the round-trip floor is around 110ms before a single router touches the packet. Real measured paths run higher, which is why AWS publishes inter-region network performance data rather than asking customers to guess.

Add a TLS handshake and a query plan and the function that executed in 5ms has been waiting for well over 100ms. Nielsen Norman Group's response time research puts the threshold for an interaction feeling instantaneous at 0.1 seconds. One cross-continent database call spends the entire budget.

Tokyo round trip

Edge latency trap

Supabase makes the same point from the other direction. Its documentation on regional invocation recommends pinning a function to the region where the database lives when the workload is database-heavy. That advice is correct, and it is also an admission. When data access dominates, you stop deploying to the edge and start deploying next to your data.

What CPU limits do and do not explain

One claim worth correcting, because it circulates widely. Cloudflare Workers are often described as capped at 10ms of CPU on the free plan and 30ms on paid. The free plan number is right. The paid number is not. Cloudflare's Workers limits now allow up to 5 minutes of CPU time per request, with a 30 second default.

The documentation also states that waiting on network requests does not count toward CPU time at all. So CPU limits are not what makes a slow database hurt. Wall-clock latency is. Your function is not being throttled while it waits. It is simply waiting, and so is your user.

Compute and data

Cold starts add a second tax on the regional side. Analysis of Lambda cold start latency puts initialization in the hundreds of milliseconds for common runtimes, and worse for JVM languages. Edge isolates largely removed that cost. The database round trip is what remains.

How SQL at the edge solves statelessness

The fix is structural. Move the storage into the network the function already runs in, and reach it through a binding instead of a connection string.

SQLite is the natural engine for this. It is embedded rather than client-server, needs no process to provision, and the SQLite project itself recommends it for exactly this profile of workload, local data storage for individual applications with modest concurrent write demand. Its one real constraint, a single writer per database, turns out to be a reasonable trade when the alternative is a 150ms hop.

Two patterns cover most of what applications need.

Shared SQL for application data. The SQL Database docs describe a standalone SQLite database that lives outside any function. You create it once, bind it by id, and query it as env.DB from as many functions as need it. There are no credentials in your code, no connection string to rotate, and no per-database deployment step. Every access path reaches one primary, so a row a function writes is visible to the next CLI query with no replica lag to reason about.

Per-entity state for coordination. Stateful Actors solve a different problem. Each actor is a single-threaded server that owns the state of a single entity, such as a shopping cart, a call leg, or a chat room. The platform runs one instance per name, routes every call for that name to it, executes methods one at a time, and persists writes before returning. A read-modify-write that would be a race condition in a normal handler is safe here, because the instance is the lock. Its SQLite surface is synchronous, since the data is right there.

Stateful edge function architecture

The zero-hop binding model

A binding is a reference the runtime resolves at deploy time. You declare the database in the function manifest, and the runtime injects it. Nothing about authentication appears in your source.

# telnyx.toml
[storage.sqldb.DB]
id = "550e8400-e29b-41d4-a716-446655440000"
import { env } from "@telnyx/edge-runtime";

type Link = { id: number; slug: string; target: string };

export default {
 async fetch(req: Request): Promise<Response> {
 const slug = new URL(req.url).searchParams.get("slug");

 const { results } = await env.DB
 .prepare(`SELECT id, slug, target FROM links WHERE slug = ?`)
 .bind(slug)
 .all<Link>();

 return Response.json({ links: results });
 },
};

There is no API key, connection string, or connection pool anywhere in this code. Compare that to the credential-based path, where the function loads a secret, opens an HTTP driver, authenticates, and then finally sends SQL. Every one of those steps is a place where latency and failure modes accumulate.

One practical note. The binding lives on the env imported from @telnyx/edge-runtime, not on the second argument to fetch(req, env). Reading it off the argument type checks cleanly and is undefined at runtime.

Querying from outside the function

The same database is reachable over a REST endpoint and the telnyx-edge CLI, so admin tooling, dashboards, and debugging sessions do not require deploying a function. A row written on the request path is readable from a terminal immediately, because both paths hit the same primary. The REST path has no parameter binding, so reserve it for SQL you wrote yourself and keep untrusted input on the prepare().bind() path.

Schema migrations

Schema is versioned in numbered .sql files and applied from the CLI before code ships. The workflow is create the database, apply migrations, then deploy a function that binds the id. Because nothing is deployed per database, shipping, rolling back, or deleting a function never touches the data.

Real-world use cases

Session management

Store sessions in edge SQL and validate them on the request path instead of calling an auth service in another region. The lookup is a single indexed read against a local primary rather than a cross-continent round trip.

Real-time personalization

Read preferences, feature flags, and entitlements at the edge and assemble the response before it leaves the site. Personalization is the workload that suffers most from a remote database, because it sits directly between the request and the first byte.

Gaming leaderboards

Rankings are read constantly and written often. A shared SQL database handles the reads, and a Stateful Actor per match or per room handles the writes that need ordering, since the actor serializes operations without a lock.

AI agent memory

Conversational agents need to remember. Every turn loads context, and every remote fetch adds delay in front of inference that is already latency sensitive. Storing conversation state next to the function removes one hop from the loop entirely.




"In the agent era, latency, reliability, and compliance aren't nice-to-haves, they are the product. If your 'intelligent system' lags 800ms, drops context when a vendor hiccups, or fails compliance checks, you're already out."

  • Ian Reither, COO at Telnyx



Dropped context is the statelessness problem stated by an operator. When an agent forgets what happened between turns, it loses the thread of the conversation, and every response it gives is generated without the context that makes it useful.

Rate limiting and counters

Count requests per IP or per API key at the edge and reject abuse before it reaches origin. Counters are small, hot, and write-heavy, which is the worst possible profile for a remote database and a good one for local storage.

Stateful edge functions compared to other approaches

Latency and location

ApproachWhere state livesTypical latency
Stateless edge function plus remote databaseCentralized region100ms and up cross-region
Edge function plus KV storageDistributed edge cacheSingle-digit ms reads
Edge function plus Telnyx SQL DatabaseEdge network, one primaryAbout 10ms warm for a simple statement
Stateful Actor with embedded SQLiteInside the actor instanceSynchronous, in-process
Azure Durable FunctionsRegional storage backendRegional round trip

Consistency and fit

ApproachConsistencyBest fit
Stateless edge function plus remote databaseStrong at the databaseExisting apps moving one route to the edge
Edge function plus KV storageEventual on writesRead-heavy lookups, config, feature flags
Edge function plus Telnyx SQL DatabaseStrong, no replica lagRelational application data shared across functions
Stateful Actor with embedded SQLiteSerialized per instanceOne entity's state, carts, rooms, call legs
Azure Durable FunctionsStrong per entityLong-running orchestration, not edge latency

A few notes on the alternatives, since none of them are wrong, they are just built for different constraints.

Cloudflare Workers with Durable Objects and D1. The closest architecture to the pattern described here. Durable Objects give per-object embedded SQLite, generally available with 10GB per object on the paid plan and 1GB on free. D1 is separate serverless SQLite with its own consistency model. The capability is real. The wiring is three products, and the telephony layer is somebody else's.

Turso. libSQL with embedded replicas, which sync a local SQLite file from a cloud primary and serve reads at microsecond latency. Writes still go to a single primary. The catch is in Turso's own embedded replicas documentation, which notes that serverless environments without a filesystem cannot use them at all. That excludes the isolate-based edge runtimes this article is about.

Neon. Serverless Postgres with an edge-compatible driver that speaks HTTP and WebSockets instead of TCP. Excellent if your team is committed to Postgres. The compute moves to the edge and the database does not, so the round trip stays.

Supabase Edge Functions. Deno functions with Postgres behind them, and documentation that recommends pinning execution to the database region for data-heavy work. That is sound engineering advice and a clear statement that the data does not travel with the compute.

Azure Durable Functions. Durable entities give small pieces of state with serialized operations per entity, which is the same coordination guarantee actors provide. It runs in Azure regions rather than at edge sites, so it solves orchestration rather than latency.

What none of them combine is state, inference, and the telephony network in one place. That is the gap the Telnyx edge compute platform is built to close, with functions, actors, SQL, KV, and GPU inference reached through the same binding surface.

Getting started with stateful edge functions

Three steps, none of which involve provisioning a server.

1. Create the database.

telnyx-edge storage sqldb create --name my-app-db

The response carries a UUID, which is the only durable handle. Provisioning finishes in a few seconds, so poll get until status reads provision_ok rather than sleeping a fixed interval.

2. Apply the schema.

telnyx-edge storage sqldb execute <database-id> --remote \
 --command "CREATE TABLE links (
 id INTEGER PRIMARY KEY,
 slug TEXT NOT NULL UNIQUE,
 target TEXT NOT NULL
 )"

Index the columns you filter and order by. Because statements against one database run one at a time, a slow query delays everything queued behind it.

3. Bind and query. Add the [storage.sqldb.DB] block to your manifest, run telnyx-edge types to generate telnyx-env.d.ts, write the handler shown above, and run telnyx-edge ship.

Plan around the documented ceilings. A database holds 1 GiB, a bound value tops out at 2 MiB, and a single result set is capped near 4 MiB, so page through large reads instead of selecting them whole. Anything large belongs in object storage with the key in SQL.

Where this runs matters as much as how it works. Telnyx operates edge compute sites across North America, Europe, APAC, and MENA on infrastructure it owns, including the telephony network and a private backbone. The platform does not run on rented CDN capacity. In-region traffic is processed by in-region compute by architecture rather than configuration, which is what makes data residency a property of the network instead of a checkbox.

Bring your state homeTelnyx runs compute, storage, inference, and telephony on the same network, so the data your function needs is already where your function runs.

Read Edge Compute docsTalk to our team

Frequently asked questions

Can edge functions have state?
Yes, when the state is co-located with the compute. A stateful edge function reads and writes persistent data through a runtime binding to storage inside the same edge network, instead of opening a connection to a database in another region. Telnyx SQL Database and Stateful Actors are the two mechanisms for it.
How do stateful edge functions differ from Durable Functions?
Azure Durable Functions orchestrate stateful workflows inside Azure regions and are built for long-running processes with checkpointing. Stateful edge functions keep state at edge sites next to the function. One optimizes for workflow durability, the other for latency.
What database do stateful edge functions use?
SQLite dominates, because it is embedded, needs no server process, and starts instantly. Telnyx SQL Database runs stock SQLite, Cloudflare D1 and Durable Objects are SQLite-backed, and Turso uses libSQL, a SQLite fork.
Are stateful edge functions consistent?
A co-located SQL database with one primary gives strong consistency. A write that resolves has committed, and every later reader sees it, with no replica lag between the function path and the API path. That is a different guarantee from an eventually consistent edge cache.
How is this different from edge caching?
Caches like KV are read-optimized and eventually consistent, which is fine for configuration and lookups and wrong for anything that has to be correct on write. Edge SQL supports transactional reads and writes with immediate visibility.
Share on Social
Eli Mogul
Eli Mogul
Content Writer & Editor

Eli is the content writer and editor at Telnyx. Born and raised in Chicago, Eli attended the University of Missouri where he obtained a BA in Journalism. Eli joined Telnyx in August of 2025. In his spare time, you'll find Eli reading, playing video games, or running.