Cloudflare
D1 vs Postgres: when SQLite at the edge is the right database
Cloudflare D1 puts SQLite next to your Workers, but it is not a drop-in Postgres replacement. Where edge SQLite wins, where Postgres still wins, and how to tell which one your workload needs.
You are building on Cloudflare Workers, you need a relational database, and you have two obvious choices: reach for Cloudflare’s own D1, or point the Worker at a Postgres instance somewhere. The marketing makes D1 sound like the natural fit and Postgres like the legacy option. That framing will lead some teams straight into a rewrite six months later. The honest version is that D1 and Postgres are good at different things, and picking well starts with understanding what SQLite at the edge actually is.
What D1 is, mechanically
D1 is SQLite, run as a managed service by Cloudflare, reachable from your Workers as a binding. SQLite is a small, battle-tested, single-file relational engine — the most widely deployed database in the world, sitting inside phones, browsers, and aircraft. It speaks standard SQL, supports transactions, indexes, and joins, and is genuinely capable. What it is not is a client-server database like Postgres, where many clients hold connections to a central server. SQLite is a library that reads and writes a file.
Cloudflare wraps that model for their platform. Each D1 database has one primary location where writes land. Reads can be served from that primary, and D1 also supports read replication, where copies of the database are kept in other regions so read queries can be answered from a location closer to the user. Writes still travel to the primary and then propagate out.
That last sentence is the whole design, and it explains everything about where D1 fits.
The latency model that decides everything
A Worker runs in the Cloudflare location nearest your visitor — hundreds of locations worldwide. Your database lives somewhere specific. The distance between those two facts is the performance story.
Reads can be fast and local. If a read replica sits in the same region as the Worker, a query is a short hop, and read-heavy workloads feel genuinely quick. This is D1 at its best: content, catalogues, configuration, lookup tables, anything read far more often than it is written.
Writes always go to the primary. A Worker in Sydney writing to a primary in North America pays the round trip, every time. One write is fine. The trouble is a request that issues eight sequential writes, each waiting on the last, each crossing an ocean. That is where an edge database can feel slower than a boring Postgres server sitting next to your application, because the physical distance is multiplied by the number of round trips.
The lesson is not “writes are slow.” It is that chatty sequential writes across a distance are slow. Batch them, and much of the cost disappears. D1 lets you send multiple statements in one batch so they make a single trip rather than one trip each. Designing for that is the difference between D1 feeling fast and feeling sluggish.
Where D1 fits
D1 is the right call when:
- Reads dominate writes. Marketing sites, documentation, product catalogues, dashboards, feature flags, per-tenant configuration. The read path is hot; the write path is occasional.
- The data volume is modest. D1 databases have size limits, and while they are generous for most application databases, they are not built to be a multi-terabyte warehouse. Split large tenants across multiple databases if you must, but if your instinct is one enormous database, that is a signal.
- You want operational simplicity. No connection pool to size, no server to patch, no separate provider to bill you. The database is part of the same platform as the Worker, deployed with the same tooling.
- Your Workers are the only client. D1 shines when the application on Workers owns the data. It is less convenient when a dozen external systems all need direct SQL access.
For a large share of the small-business software we build — booking tools, internal dashboards, content-driven sites, per-customer settings — D1 is a clean fit. The workload is read-heavy, the data is bounded, and keeping the whole stack inside Cloudflare removes a category of operational work.
Where Postgres still wins
Postgres is the right call when:
- Writes are heavy or highly concurrent. A system taking a constant stream of writes from many clients wants a real client-server database with a mature concurrency and locking model. This is not SQLite’s strength.
- The queries are complex. Window functions, recursive CTEs, sophisticated query planning over large tables, full-text search that needs to be serious. Postgres has decades of work in its planner and a feature set SQLite does not try to match.
- You need extensions. PostGIS for geospatial work,
pgvectorfor embeddings, and a long list of others. If your application leans on one of these, that decides it — SQLite has no equivalent. - Data outgrows a single file’s comfort zone. Large datasets, heavy analytical queries, and workloads that expect to scale vertically on a big server are Postgres territory.
- Many services share one database. When the database is a shared point of integration for several applications and external tools, the client-server model is what you want.
Postgres also pairs with Workers perfectly well. Cloudflare Hyperdrive exists precisely to make a Worker-to-Postgres connection fast and pooled, so “we are on Workers” is not by itself a reason to avoid Postgres. You get the edge compute and keep the heavyweight database.
Migration considerations, in both directions
If you are moving to D1, the SQL dialect is close but not identical to Postgres. Types are looser in SQLite, some functions differ, and anything relying on a Postgres extension has no direct equivalent. Audit your queries before assuming a clean port. Re-examine every place your code does sequential writes, because what was cheap against a local Postgres server may need batching against a distant D1 primary.
If you are moving off D1 to Postgres — usually because writes or query complexity outgrew it — the schema translates readily, and the harder work is often on the application side, adopting connection pooling and reconsidering assumptions that only held because the database was a local file.
The most useful thing you can do before either move is to be honest about your read-to-write ratio and your query shapes under real load, not the load you imagine. Most “the database is slow” problems we see are not the database. They are an application making ten sequential trips where one batched trip would do, and that pattern hurts on D1 and Postgres alike.
How to choose
Start with the workload, not the brand. Read-heavy, modest data, Workers-owned, operationally simple: D1, and design your writes to batch. Write-heavy, complex queries, extensions, shared access, or large data: Postgres, fronted by Hyperdrive if you are on Workers. Plenty of real systems end up with both — D1 for the read-hot edge data and Postgres for the transactional core.
If you are weighing this for something you are about to build, that architecture call is worth getting right before the first table exists, because it is far cheaper to choose than to migrate. It is the kind of decision we work through with clients as part of how we build software, and we do it on Cloudflare’s platform most days.
Send us two paragraphs about your data and how it is read and written, and we will reply in writing within one business day.