Skip to content

Edge Computing Overview

Why Edge Computing Exists

Edge computing moves computation from centralized data centers to points of presence (POPs) close to end users. The motivation is physics: the speed of light in fiber optic cable is ~200,000 km/s, making a round-trip from New York to Singapore (~15,000 km each way) take at minimum 150ms. No amount of code optimization can beat this latency floor.

Edge computing eliminates the geography penalty by running code at 200+ locations worldwide, typically within 50ms of every internet user. The tradeoff: edge environments have limited resources, no persistent filesystem, and constrained runtime APIs compared to traditional servers.

The Edge Computing Spectrum

Historical Context

  • 1999: Akamai introduces EdgeSuite for executing code at the edge (proprietary, limited).
  • 2017: Cloudflare Workers launches — V8 isolates at the edge. Game changer for developer accessibility.
  • 2018: AWS Lambda@Edge — run Lambda functions at CloudFront POPs.
  • 2019: Fastly Compute@Edge — WebAssembly at the edge.
  • 2020: Deno Deploy enters beta — globally distributed V8 isolates.
  • 2021: Vercel Edge Functions, Netlify Edge Functions.
  • 2022+: Edge databases (Cloudflare D1, Durable Objects, Turso, Neon branching), making full-stack edge applications viable.

First Principles

The Latency Hierarchy

Ttotal=TDNS+TTCP+TTLS+Trequest+Tcompute+Tresponse

For a traditional centralized server serving a user 5,000km away:

ComponentCentralizedEdge
DNS lookup20-50ms5-10ms (anycast)
TCP handshake50ms5ms
TLS handshake50ms5ms
Request transit25ms2ms
Compute5-50ms5-50ms
Response transit25ms2ms
Total175-250ms24-74ms

Edge reduces network-related latency by 75-90%, while compute time remains the same.

V8 Isolates vs Containers

The key innovation enabling edge computing is the V8 isolate model. Instead of running each function in a separate container (Lambda model), edge platforms run many functions in the same process using V8 isolates:

CharacteristicContainer (Lambda)Isolate (Workers)
Cold start100-1000ms0-5ms
Memory overhead50-200MB128KB-128MB
Startup isolationProcess-levelV8 isolate
Max instances per host10-501,000-10,000
Available APIsFull Node.jsWeb Standards subset
Filesystem accessYes (ephemeral)No
Native addonsYesNo

The CAP Theorem at the Edge

Edge computing distributes state across geographically distant locations, making CAP theorem tradeoffs explicit:

Consistency+Availability+Partition Tolerance2

Edge systems typically choose AP (Availability + Partition tolerance), accepting eventual consistency. When the edge cannot reach the origin, it serves stale data rather than returning errors.

Core Mechanics

Request Flow at the Edge

Edge Function Lifecycle

typescript
// Edge functions follow the Service Worker pattern:
// 1. Receive a Request
// 2. Return a Response
// No persistent state between requests (stateless)

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // ctx.waitUntil() for background work after response is sent
    // env contains bindings (KV, D1, Durable Objects, etc.)

    const url = new URL(request.url);

    // Route handling at the edge
    if (url.pathname.startsWith('/api/')) {
      return handleApi(request, env);
    }

    if (url.pathname.startsWith('/static/')) {
      return handleStatic(request, env, ctx);
    }

    // Default: proxy to origin
    return fetch(request);
  },
};

async function handleApi(request: Request, env: Env): Promise<Response> {
  // Read from edge KV store (eventual consistency, ~60ms global propagation)
  const cached = await env.CACHE_KV.get('api:data', 'json');
  if (cached) {
    return Response.json(cached, {
      headers: { 'X-Cache': 'HIT' },
    });
  }

  // Fetch from origin
  const response = await fetch('https://api.origin.com/data');
  const data = await response.json();

  // Cache at edge (non-blocking)
  await env.CACHE_KV.put('api:data', JSON.stringify(data), {
    expirationTtl: 300, // 5 minutes
  });

  return Response.json(data, {
    headers: { 'X-Cache': 'MISS' },
  });
}

Edge State Management Patterns

Implementation: Full-Stack Edge Application

typescript
// A complete edge application: API + static assets + caching + auth

interface Env {
  STATIC_ASSETS: KVNamespace;
  API_CACHE: KVNamespace;
  AUTH_DB: D1Database;
  RATE_LIMITER: DurableObjectNamespace;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    try {
      // Rate limiting using Durable Objects
      const rateLimitResult = await checkRateLimit(
        request, env.RATE_LIMITER
      );
      if (!rateLimitResult.allowed) {
        return new Response('Too Many Requests', {
          status: 429,
          headers: { 'Retry-After': String(rateLimitResult.retryAfter) },
        });
      }

      // Route
      if (url.pathname.startsWith('/api/')) {
        return handleApi(request, env, ctx);
      }

      // Static assets from KV
      return handleStatic(url.pathname, env);
    } catch (error) {
      return new Response('Internal Server Error', { status: 500 });
    }
  },
};

async function handleStatic(
  path: string,
  env: Env
): Promise<Response> {
  const asset = await env.STATIC_ASSETS.get(path, 'arrayBuffer');
  if (!asset) {
    return new Response('Not Found', { status: 404 });
  }

  const contentType = getContentType(path);
  return new Response(asset, {
    headers: {
      'Content-Type': contentType,
      'Cache-Control': path.includes('.') && !path.endsWith('.html')
        ? 'public, max-age=31536000, immutable'
        : 'public, max-age=0, must-revalidate',
    },
  });
}

async function handleApi(
  request: Request,
  env: Env,
  ctx: ExecutionContext
): Promise<Response> {
  const url = new URL(request.url);

  // Auth check
  const authHeader = request.headers.get('Authorization');
  if (!authHeader) {
    return new Response('Unauthorized', { status: 401 });
  }

  // Verify JWT at the edge (no round-trip to auth server)
  const user = await verifyJwt(authHeader, env);
  if (!user) {
    return new Response('Invalid token', { status: 403 });
  }

  // API routing
  if (url.pathname === '/api/profile' && request.method === 'GET') {
    // Read from edge D1 database
    const profile = await env.AUTH_DB.prepare(
      'SELECT * FROM users WHERE id = ?'
    ).bind(user.id).first();

    return Response.json(profile);
  }

  return new Response('Not Found', { status: 404 });
}

function getContentType(path: string): string {
  const ext = path.split('.').pop()?.toLowerCase();
  const types: Record<string, string> = {
    html: 'text/html',
    css: 'text/css',
    js: 'application/javascript',
    json: 'application/json',
    png: 'image/png',
    jpg: 'image/jpeg',
    svg: 'image/svg+xml',
    woff2: 'font/woff2',
  };
  return types[ext || ''] || 'application/octet-stream';
}

Edge Cases and Failure Modes

1. Cold Start on Uncommon POPs

typescript
// Edge functions at rarely-accessed POPs may have higher cold starts
// because the V8 isolate was evicted from memory

// Mitigation: Pre-warm critical POPs with synthetic traffic
// Most platforms don't need this, but Lambda@Edge can have 1-5s cold starts

2. Edge-Origin Consistency Window

typescript
// User writes to origin, then reads from edge — sees stale data
// Because edge cache/KV hasn't been updated yet

// Solution 1: Sticky sessions — route writes and subsequent reads to origin
// Solution 2: Read-after-write consistency header
// Solution 3: Optimistic updates at edge with eventual sync

async function handleUpdate(request: Request, env: Env): Promise<Response> {
  const data = await request.json();

  // Write to origin
  const response = await fetch('https://origin.example.com/api/update', {
    method: 'POST',
    body: JSON.stringify(data),
  });

  // Immediately update edge cache so the user sees their own write
  if (response.ok) {
    await env.CACHE_KV.put(
      `user:${data.userId}`,
      JSON.stringify(data),
      { expirationTtl: 300 }
    );
  }

  return response;
}

3. Global Rate Limiting at the Edge

typescript
// Each edge POP has its own rate limit counter — users can exceed
// the global limit by hitting different POPs

// Solution: Use Durable Objects for global rate limiting
// Each user's rate limit state lives on a single Durable Object
// All edge POPs route to the same DO instance

War Story

The Edge Function That Called Itself

A team deployed a Cloudflare Worker that proxied API requests to their origin server. The origin server was also behind Cloudflare. The Worker fetched from the same hostname, which routed back through the Worker, creating an infinite loop. Cloudflare detected this and returned a 522 error, but the team spent hours debugging "connection timeout" errors.

The fix was using the origin server's direct IP address (or a separate hostname not proxied through Cloudflare) for the fetch call.

War Story

The KV Consistency Surprise

An edge application stored user sessions in Cloudflare KV. A user logged in (KV write from US-East POP), then was routed to US-West POP for their next request. The KV read returned null because the write hadn't propagated yet (~60 second eventual consistency). The user was forced to log in again.

The fix was using Durable Objects for session state (strong consistency within a region) instead of KV, or implementing a "read your own writes" pattern where the login response included a signed cookie containing the session data that the edge function could validate without KV.

Performance Characteristics

Latency Comparison

OperationCentralized (us-east-1)Edge (nearest POP)
Static asset50-200ms5-20ms
API proxy100-300ms10-50ms
KV readN/A1-5ms
D1 queryN/A2-10ms
Origin fetch5-20ms50-200ms (from edge)

Cost Model

Edge Cost=R×Cr+B×Cb+S×Cs

Where:

  • R = number of requests, Cr = cost per request (~$0.15/M for Workers)
  • B = bandwidth, Cb = cost per GB (~$0.05)
  • S = storage, Cs = cost per GB-month (~$0.50 for KV)

For 100M requests/month with 500GB bandwidth:

Cost=100×0.15+500×0.05+1×0.50=$15+$25+$0.50=$40.50

Compared to a containerized server at $200-500/month for similar traffic.

Decision Framework

When to Use Edge Computing

Use CaseEdge BenefitPlatform
Static site + API proxyHigh (latency reduction)Any CDN + edge functions
A/B testingHigh (no origin latency)Workers, Vercel Edge
Auth/JWT validationHigh (reduce origin load)Workers, Lambda@Edge
PersonalizationMedium (geo, device detection)Workers, Vercel Edge
Full APIMedium (if data can be at edge)Workers + D1/DO
Real-time collaborationLow (needs WebSocket at origin)Durable Objects only
Heavy computationLow (limited CPU time)Use origin/serverless
Relational queriesMedium (edge DBs are new)D1, Turso, Neon

When NOT to Use Edge

  • Long-running computations (>30s) — edge functions have strict CPU time limits
  • Large file processing — limited memory (128MB typical)
  • Complex database queries — edge databases are limited
  • Native binary dependencies — no filesystem, no native addons
  • Stateful WebSocket servers — only Durable Objects support this
  • Compliance-restricted data — data locality requirements may conflict with edge distribution

Advanced Topics

Edge-First Architecture

The edge-first architecture handles 80-95% of requests entirely at the edge:

  1. Static assets: Served from edge KV or R2 (object storage).
  2. API reads: Served from edge cache, KV, or D1 (SQLite at edge).
  3. API writes: Forwarded to origin, with edge cache invalidation.
  4. Personalization: Computed at edge using geo, device, and cookie data.
  5. Auth: JWT validation at edge, no origin round-trip.

Key Takeaway

Edge computing is not a replacement for traditional servers — it is a complement that handles the latency-sensitive portion of your stack. Start by moving static assets and API caching to the edge, then progressively move authentication, personalization, and eventually data to the edge. The platforms are maturing rapidly, with edge databases making full-stack edge applications increasingly viable.

Section Contents

Cross-References

"What I cannot create, I do not understand." — Richard Feynman