← Back to all articles
The Database Deep Dive — Issue 001

Neon vs Cloudflare

A complete architectural breakdown of Neon's serverless Postgres and Cloudflare's edge data platform — blobs, functions, D1, KV, R2, and when each changes everything.

Published August 2026 · Reviewed August 2026
Depth Deep Dive
Category Database Architecture
Read Time ~15 min
ansi · wordmark · database
██████╗  █████╗ ████████╗ █████╗ ██████╗  █████╗ ███████╗███████╗
██╔══██╗██╔══██╗╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝
██║  ██║███████║   ██║   ███████║██████╔╝███████║███████╗█████╗  
██║  ██║██╔══██║   ██║   ██╔══██║██╔══██╗██╔══██║╚════██║██╔══╝  
██████╔╝██║  ██║   ██║   ██║  ██║██████╔╝██║  ██║███████║███████╗
╚═════╝ ╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝╚═════╝ ╚═╝  ╚═╝╚══════╝╚══════╝

// The Landscape

Neon
Serverless Postgres that separates storage from compute. Branches databases like Git. Scales to zero. Loved by developers who want real SQL without infrastructure tax.
Postgres Serverless Branching SQL Scale-to-Zero
Cloudflare
A global edge network that sprouted a full data platform. Workers, D1 (SQLite), KV, R2, Queues, Durable Objects — all co-located with your compute at 300+ edge locations.
Edge-First D1 / SQLite KV Store R2 Blobs Workers
The Core Tension

Neon asks: "What if Postgres was serverless-native?" Cloudflare asks: "What if your database lived at the edge, next to your users?" Both solve the "I want managed data without servers" problem — but from radically different starting points. The architectural choices that follow from those two questions diverge completely.

// Neon: Architecture Deep Dive

Neon (still available at neon.tech; announced acquisition by Databricks in 2025 while continuing as a developer Postgres product) is not just "Postgres on AWS." It's a ground-up reimagining of how a relational database should work when you don't want to think about instances, storage provisioning, or connection pooling.

Neon Architecture — Storage/Compute Separation
┌──────────────────────────────────────────────────────────┐
│                     CLIENT APPLICATION                    │
│              (any Postgres-compatible driver)            │
└─────────────────────────┬────────────────────────────────┘
                           │  standard pg wire protocol
                           ▼
┌──────────────────────────────────────────────────────────┐
│                   NEON PROXY LAYER                        │
│         Connection pooling · Auth · Routing              │
│         HTTP-over-SQL for edge environments              │
└──────────┬─────────────────────────────────┬─────────────┘
           │                                 │
           ▼                                 ▼
┌──────────────────────┐       ┌─────────────────────────┐
│   COMPUTE NODE       │       │   COMPUTE NODE          │
│   (Postgres 16)      │       │   (Postgres 16)         │
│   Active/Running     │       │   SUSPENDED (cold)      │
│   ← your primary     │       │   ← wakes in ~500ms     │
└──────────┬───────────┘       └────────────┬────────────┘
           │  WAL (Write-Ahead Log)          │
           └────────────┬────────────────────┘
                        ▼
┌──────────────────────────────────────────────────────────┐
│                   SAFEKEEPER LAYER                        │
│        Distributed WAL storage (3-node quorum)           │
│        Durability guarantee before ack to compute        │
└─────────────────────────┬────────────────────────────────┘
                           │  async page stream
                           ▼
┌──────────────────────────────────────────────────────────┐
│                    PAGESERVER                             │
│    Object storage (S3) + in-memory page cache            │
│    Stores ALL historical versions of every page          │
│    Enables: branching · PITR · instant forks             │
└──────────────────────────────────────────────────────────┘

Compute/Storage Separation

The most radical thing Neon does. Traditional Postgres fuses compute and storage together — your data lives on the same machine running queries. Neon separates them entirely. Compute nodes (plain Postgres processes) talk to a remote Pageserver over a custom protocol. This means compute can scale to zero, wake up in a few hundred milliseconds (often ~200–500ms depending on region and plan), and multiple computes can read the same storage simultaneously.

The Pageserver — Where All the Magic Lives

The Pageserver stores every page version ever written, backed by S3. This enables Neon's three killer features: (1) Point-in-time recovery to any second in your retention window, (2) Database branching that's instant and copy-on-write (no data duplication until you write), and (3) Time-travel queries with AS OF syntax. Every branch is just a pointer to a historical page version.

Safekeeper — WAL Durability Without a Primary

Before any write is acknowledged, it's committed to a 3-node Safekeeper quorum — think of it as a distributed WAL buffer. This decouples durability from compute availability. Your compute can crash; the WAL is safe. When a new compute wakes up, it replays from the Safekeeper to get current.

Neon Proxy & HTTP-over-SQL

For environments that can't maintain persistent TCP connections (serverless functions, edge runtimes), Neon exposes an HTTP endpoint where you send SQL in JSON. No persistent connection, no pooling needed. This is how Neon works natively in Vercel Edge Functions, Cloudflare Workers, or any fetch-based runtime.

01
Query Arrives
Client connects via pg wire protocol or HTTP endpoint. Proxy routes to correct branch/compute.
02
Compute Wakes
If suspended, compute node starts. cold start (typically a few hundred ms). Warm starts are instant. Hot pool for always-on.
03
Pages Fetched
Postgres requests only needed pages from Pageserver. Cache warms progressively per query pattern.
04
WAL Written
Mutations go to WAL → Safekeeper quorum ack → async flush to Pageserver object storage.
05
Response Sent
Result returned. Compute stays warm for inactivity window, then suspends to save cost.

// Neon: Blobs, Extensions & Functions

Neon is Postgres — which means it inherits the entire Postgres extension ecosystem. "Blobs" in Neon are handled natively through standard SQL types and the Pageserver's object storage backend.

Binary Data / Blobs
  • BYTEA column type — raw binary in-row storage
  • Large Objects via pg_largeobject catalog
  • External storage via pg_s3 / foreign data wrappers
  • pgvector for embedding vectors (binary-dense blobs)
  • All backed by Pageserver → S3 at storage layer
  • Blob data participates in branching and PITR
Postgres Extensions (Neon Supported)
  • pgvector — vector similarity search
  • PostGIS — geospatial queries
  • pg_trgm — fuzzy text search
  • timescaledb — time-series data
  • pg_cron — scheduled SQL jobs
  • pgcrypto — encryption functions

Neon Functions = Postgres Functions / Procedures

Neon doesn't have a separate "functions" product. Your logic lives in Postgres itself via PL/pgSQL, PL/Python, or PL/v8 (JavaScript). You define stored procedures, triggers, and custom aggregates directly in SQL. This is powerful for data-adjacent logic (validation, auto-computed columns, audit trails) but these are not HTTP-accessible serverless functions — they're database-internal.

Neon — PL/pgSQL function example
-- A stored function that runs inside Neon Postgres CREATE OR REPLACE FUNCTION calculate_user_tier(user_id UUID) RETURNS TEXT AS $$ DECLARE total_spend NUMERIC; BEGIN SELECT SUM(amount) INTO total_spend FROM orders WHERE orders.user_id = $1 AND created_at > NOW() - INTERVAL '30 days'; RETURN CASE WHEN total_spend > 1000 THEN 'gold' WHEN total_spend > 100 THEN 'silver' ELSE 'bronze' END; END; $$ LANGUAGE plpgsql; -- Call it like any SQL function SELECT calculate_user_tier('abc-123-def');

Database Branching — Git for Your Data

This is Neon's flagship feature. Creating a branch is instantaneous — it's a metadata operation, not a data copy. Your CI/CD pipeline can spin up a full production-data branch for every PR, run migrations, run tests, and tear it down. No more "test against staging which diverged 3 months ago." Branch from any point in time. Merge is manual (apply migrations back to main).

Neon — HTTP-over-SQL for serverless/edge environments
// Works in Cloudflare Workers, Vercel Edge, Deno Deploy // No persistent connection needed import { neon } from '@neondatabase/serverless'; const sql = neon(process.env.DATABASE_URL); // Single round-trip HTTP query const users = await sql` SELECT id, name, email FROM users WHERE active = true LIMIT 10 `; // Works as tagged template literal — SQL injection safe
Neon's Secret Superpower

Because Neon is real Postgres, every existing Postgres tool works: Prisma, Drizzle, SQLAlchemy, ActiveRecord, pg_dump, pg_restore, pgAdmin, DataGrip, Postico. Zero migration cost from existing Postgres stacks. You don't learn a new query language. You don't change your ORM. You just change your connection string.

// Cloudflare: Architecture Deep Dive

Cloudflare's data products weren't planned from day one — they grew organically from the need to give Workers (their edge compute) somewhere to put state. The result is a portfolio of purpose-built primitives, each optimized for a specific access pattern.

Cloudflare Data Platform — Edge-Distributed Architecture
                    ┌─────────────────────┐
                    │    CLIENT REQUEST   │
                    └──────────┬──────────┘
                               │
                    ┌──────────▼──────────┐
                    │  CLOUDFLARE NETWORK │
                    │  300+ PoPs global   │
                    │  Anycast routing    │
                    └──────┬───────┬──────┘
                           │       │
             ┌─────────────▼─┐   ┌─▼──────────────┐
             │ WORKERS       │   │ WORKERS         │
             │ (V8 Isolate)  │   │ (V8 Isolate)    │
             │ Tokyo PoP     │   │ Frankfurt PoP   │
             └──┬─────┬──────┘   └──┬──────┬───────┘
                │     │             │      │
         ┌──────▼─┐ ┌─▼───────┐ ┌──▼──┐ ┌─▼────────┐
         │  KV    │ │Durable  │ │ D1  │ │  R2      │
         │ Store  │ │Objects  │ │(SQL)│ │ (Blobs)  │
         └──────┬─┘ └─────────┘ └──┬──┘ └──────────┘
                │                   │
         ┌──────▼───────────────────▼──────────┐
         │      CLOUDFLARE GLOBAL STORAGE       │
         │  Replicated across regions/PoPs      │
         │  Consistency model varies by product │
         └─────────────────────────────────────┘

Workers — The Compute Foundation

Everything runs inside Workers — V8 isolates (not containers, not VMs) that start in under 1ms. Workers are the execution environment that calls your data primitives. The crucial architectural insight: your compute is already at the edge, so your data access needs to be too. High-latency database calls to a centralized server kill the edge performance advantage.

Durable Objects — The Consistency Primitive

The most architecturally interesting Cloudflare product. A Durable Object is a single-instance JavaScript class that lives at one location in the world and serializes all access to its state. It solves distributed coordination without distributed locks. Think: a shopping cart, a game room, a real-time document — anything that needs one authoritative owner. State is transactionally consistent within the object; all writes go to one place.

Workers KV — Globally Distributed Key-Value

KV is Cloudflare's eventually-consistent global cache. Write once, replicate everywhere. Reads return in <1ms from any PoP because data is cached at every edge node. Writes propagate globally within ~60 seconds. Perfect for: feature flags, session tokens, user preferences, public config. Not perfect for: anything that needs strong consistency or frequent writes.

D1 — SQLite at the Edge

D1 is Cloudflare's relational database product. It's SQLite-compatible, meaning you use standard SQL — but SQLite, not Postgres. D1 co-locates with Workers as SQLite-backed SQL. Cloudflare has shipped global read replication (Sessions API; async replicas with possible lag)—writes still go through a single primary, and each database remains size/throughput constrained relative to full Postgres. D1 is excellent for Workers-native, read-heavy, moderate-scale apps; Neon remains the clearer fit when you need the Postgres dialect, extensions (e.g. pgvector), branching, or large transactional SaaS schemas.

// Cloudflare Data Primitives: Blobs, KV, Queues

R2 — Object Storage (The Blob Layer)

R2 is Cloudflare's S3-compatible object storage. The key differentiator: zero egress fees. With S3, you pay to read your own data out. With R2, egress is free. Store images, videos, backups, ML model weights, CSVs. Access from Workers at very low latency. R2 has an S3-compatible API so your existing tooling works immediately.

R2 — When to Use It
  • User-uploaded media (images, docs, videos)
  • Static asset delivery at the edge
  • ML model weight storage
  • Database backups / data lake
  • Multi-region file distribution
  • Zero-egress cost large file hosting
R2 — S3 API Compatibility
  • PutObject / GetObject
  • Multipart uploads (large files)
  • Presigned URLs for direct upload
  • Bucket lifecycle policies
  • CORS configuration
  • Custom domains via Workers
Cloudflare R2 — Upload & Retrieve Blobs from a Worker
// wrangler.toml: [[r2_buckets]] // binding = "MY_BUCKET", bucket_name = "uploads" export default { async fetch(request, env) { const url = new URL(request.url); const key = url.pathname.slice(1); if (request.method === 'PUT') { // Store blob directly from request body await env.MY_BUCKET.put(key, request.body, { httpMetadata: { contentType: request.headers.get('content-type') } }); return new Response('Uploaded', { status: 200 }); } // Retrieve blob — served from edge cache const obj = await env.MY_BUCKET.get(key); if (!obj) return new Response('Not Found', { status: 404 }); return new Response(obj.body, { headers: { 'content-type': obj.httpMetadata?.contentType ?? 'application/octet-stream' } }); } };

Workers KV — Global Config & Session State

KV is a write-once, read-everywhere store. Values up to 25MB per key. Ideal for data that changes infrequently but needs to be read millions of times per second globally. The eventual consistency model (60s propagation) is a design constraint to work around, not against.

Cloudflare KV — Feature Flags Pattern
// Write from your CI/CD pipeline await env.FLAGS.put('dark_mode_rollout', JSON.stringify({ enabled: true, percentage: 25, updated: Date.now() }), { expirationTtl: 3600 }); // Auto-expire in 1 hour // Read at every edge PoP — sub-millisecond const flag = JSON.parse(await env.FLAGS.get('dark_mode_rollout')); if (flag?.enabled && Math.random() < flag.percentage / 100) { // Serve dark mode variant }

Queues — Async Message Passing

Cloudflare Queues is a durable, at-least-once message queue for Workers. Enqueue work from one Worker, process it in a consumer Worker. Dead letter queues, retry logic, and batch processing built in. Think: email sending, webhook processing, background jobs — without managing Redis or SQS.

Workers AI — ML Inference at the Edge

Cloudflare runs inference workloads (text generation, embeddings, image classification) at edge PoPs. You call models like LLaMA or Whisper from a Worker with a simple binding — no external API calls, no cold starts on GPU instances. The data never leaves the edge layer.

Cloudflare's Unique Moat

The edge network itself. When your Worker runs in Tokyo, your KV read, your R2 fetch, your D1 query — all of it is local to that PoP. The physics advantage is real: a centralized Postgres instance in us-east-1 will always add 150ms+ latency for a Tokyo user. Cloudflare's answer is to bring the database to the user.

// Head-to-Head: Full Comparison

Dimension 🌿 Neon 🔶 Cloudflare
Database Engine Full PostgreSQL 16 SQLite (D1) / Custom (KV, DO)
Query Language Standard SQL + all PG syntax SQLite SQL dialect (D1)
Compute Location Regional (AWS us-east, eu-west, etc.) 300+ edge PoPs globally
Cold Start few hundred ms (compute wake) / instant if warm <1ms (V8 isolate)
Scale to Zero Native, automatic Always (Workers stateless)
Database Branching Instant, copy-on-write Not available
Point-in-Time Recovery Any second in window ~ D1 has backup/restore
Object / Blob Storage ~ Via BYTEA / external FDW R2 (S3-compatible, zero egress)
Global KV / Cache Not native Workers KV (eventual consistency)
Consistency Model Strong (ACID Postgres) Strong (D1 primary) / Eventual (KV, replicas)
Extensions Ecosystem Full Postgres extensions No extension model
Message Queue Not native Cloudflare Queues
Serverless Functions ~ Postgres stored procedures Workers (full JS/TS/Rust runtime)
Read Latency (local) 1–5ms (warm, same region) <1ms (edge KV / D1 replica)
Complex Joins Full query planner ~ D1 supports joins, limited optimizer
Connection Protocol pg wire + HTTP-over-SQL Native Workers bindings (no TCP)
ORM Compatibility Any Postgres ORM ~ Drizzle, D1-specific adapters
ML / Vector Search pgvector extension Workers AI + Vectorize
Free Tier Generous (compute hours + 0.5GB) Very generous (Workers, R2, D1 free tiers)
Egress Pricing Standard cloud egress Zero egress (R2, KV)
Multi-tenant Support Branches per tenant Durable Objects per tenant
D1 vs Neon — The SQLite Limit

D1 uses SQLite syntax. If you're migrating from Postgres, you'll hit dialect differences: no JSONB aggregation, limited window functions, no ARRAY type, no custom aggregates, no pg_cron, no PostGIS. For new projects this is fine; for Postgres migrations, Neon wins hands-down.

// Use Cases: When to Choose What

Neon Wins
SaaS Application Backend
Full ACID Postgres for user data, billing, permissions. Complex relational queries, transactions, foreign keys. Prisma/Drizzle ORM. Branch per PR for safe migrations. Neon is the clear choice when you need a "real" database.
Cloudflare Wins
Global Media / CDN Delivery
User-uploaded images, videos, PDFs. R2 for zero-egress blob storage. Workers transform, resize, optimize on the fly. KV caches metadata. End users get sub-50ms asset delivery from their nearest PoP.
Cloudflare Wins
Real-Time Multiplayer / Collaboration
Durable Objects serialize all state updates for a game room, doc session, or live feed. One owner = strong consistency without distributed locking complexity. WebSocket connections managed at the edge.
Neon Wins
AI / RAG Applications
pgvector stores and queries embeddings alongside structured metadata in the same Postgres query. Join your embeddings with user data, permissions, timestamps. Hybrid search (vector + BM25 text) in a single SQL statement.
Both Work
Next.js / Remix Full-Stack Apps
Both platforms have first-class integrations. Neon with Vercel, Cloudflare with their own Pages. If deploying to Cloudflare Pages/Workers: D1 wins for convenience. If deploying to Vercel/Netlify: Neon wins. Pick your deployment platform first.
Neon Wins
CI/CD Database Testing
Instant database branches per PR. Your test suite runs against real production data (branch is copy-on-write, production untouched). Migration scripts tested before merge. No more "works on staging, breaks on prod."
Cloudflare Wins
API Rate Limiting / Feature Flags
KV stores rate limit counters and feature flag configs globally. Durable Objects provide per-user rate limiting with strong consistency. No external Redis needed. All runs natively in the Workers runtime.
Both Work
Multi-Tenant SaaS
Neon: database branch per tenant for full isolation + cost efficiency. Cloudflare: Durable Object per tenant for strong consistency + edge proximity. Neon wins on data complexity; Cloudflare wins on global latency requirements.
Cloudflare Wins
Background Job Processing
Cloudflare Queues + Workers Consumer. Enqueue from your API, process asynchronously. Email sends, webhooks, data processing, report generation. No Redis/SQS/Celery infrastructure to manage.
✓ Neon: Migrations
Branch → migrate → test → merge. Git-style workflow for schema changes. No more migration fear.
✓ CF: Edge AI Inference
Workers AI runs LLaMA 3, Whisper, CLIP at PoPs. Inference latency without GPU cold starts.
✓ Neon: PITR
Restore to any second. Fat-finger a DELETE? Travel back in time. Zero data loss incidents.
✓ CF: Zero Egress R2
S3 egress fees can be 50-90% of storage bills. R2 eliminates this entirely. Huge cost lever.
✓ Neon: pgvector
Vector + relational in one query. Semantic search filtered by user permissions. One database.
✓ CF: Durable Objects
Stateful edge actors. The only way to do strong-consistency real-time collaboration at the edge.

// The Verdict

Choose Neon when...
  • You need real Postgres (not SQLite)
  • Your team already uses Postgres ORMs
  • Complex queries, joins, aggregations matter
  • CI/CD database branching is valuable
  • pgvector / PostGIS / extensions needed
  • Point-in-time recovery is a requirement
  • You want full ACID guarantees globally
  • AI/RAG workloads mixing vectors + SQL
Choose Cloudflare when...
  • Global latency (<50ms worldwide) matters
  • You're building on Workers/Pages already
  • Blob/media storage without egress fees
  • Real-time features (WebSockets, sync)
  • You need compute + data at the edge
  • Background queues / async processing
  • Simple key-value state globally
  • Edge AI inference on your own data
The Synthesis
They're not competitors — they're complements
The best architecture for a globally-distributed app might be: Neon as the source-of-truth Postgres for complex business data, + Cloudflare R2 for user-uploaded blobs, + Workers KV for session/cache, + Cloudflare Workers calling Neon over HTTP-over-SQL from the edge. This is a real, production pattern that many teams run today.

The Neon + Cloudflare Stack

Neon explicitly supports being called from Cloudflare Workers via its HTTP-over-SQL driver. Your Worker handles the edge routing, auth, caching (KV), and blob serving (R2). For anything requiring relational queries, it fires an HTTP request to Neon and gets back SQL results. You get edge performance for cached/simple reads and full Postgres power for complex queries. No compromises.

The Combined Stack — Cloudflare Worker + Neon Postgres
// Cloudflare Worker using Neon's HTTP driver import { neon } from '@neondatabase/serverless'; export default { async fetch(request, env) { const url = new URL(request.url); // 1. Check KV cache first (sub-ms from edge) const cached = await env.CACHE.get(url.pathname); if (cached) return new Response(cached, { headers: { 'x-cache': 'HIT' } }); // 2. Query Neon Postgres via HTTP (no TCP connection) const sql = neon(env.DATABASE_URL); const data = await sql` SELECT p.*, u.name as author_name FROM posts p JOIN users u ON p.user_id = u.id WHERE p.slug = ${url.pathname.slice(1)} AND p.published = true `; if (!data.length) return new Response('Not Found', { status: 404 }); // 3. Cache result in KV for 5 minutes const result = JSON.stringify(data[0]); await env.CACHE.put(url.pathname, result, { expirationTtl: 300 }); return new Response(result, { headers: { 'content-type': 'application/json', 'x-cache': 'MISS' } }); } };
// Share this deep dive

Send with a live card — iMessage, SMS, X, Facebook, WhatsApp, Instagram, TikTok.