Univa Learning

Module 5 of 9 Infra

Serverless and Edge Compute

Level Intermediate Estimated study time 2-3 evenings Prerequisites comfort with HTTP requests, basic cloud terms (function, request, response)
Video lesson: coming soon

Why this matters for Univa

univa.my already runs on this layer:

  • Cloudflare Pages serves the static site.
  • Cloudflare Workers (deployed via GitHub Actions and wrangler) handle the dynamic parts.
  • Client Next.js apps run their API routes as serverless functions on Vercel.

Univa has also already hit the sharp edge of this model in production: the Workers free plan's 10ms CPU limit, tripped by bursty cold-start probing that returned error 1102 for everyone hitting the Worker at the wrong moment (see reference_igg_worker_cpu_cliff in memory). That was not a bug. It was the free tier working exactly as designed, and nobody on the team had a clear model of why until after the incident.

This module builds the mental model needed to answer three questions Univa faces on every build:

  1. Where should this piece of logic run?
  2. What happens when it gets popular?
  3. What breaks first, and why?

Core concepts

What "serverless" actually means

There are still servers. Univa just does not manage them. The platform (AWS, Cloudflare, Vercel, Google) owns provisioning, patching, and scaling. Univa ships a function; the platform decides how many copies to run and where.

Two properties follow from this:

  • Scale to zero. No traffic means no running instances, and often no cost.
  • Pay per use. Billing tracks requests, execution time, or CPU time, not a fixed monthly server rent.

Event-driven thinking

A serverless function does not sit there listening. It is dormant until an event wakes it up:

  • an HTTP request
  • a file upload
  • a queue message
  • a cron schedule
  • a database change

The function runs, returns a result, and goes back to sleep. Design each function as event in, response out, not as a long-lived process with its own internal lifecycle or background loop.

Statelessness

Nothing guarantees the same physical instance handles the next request. Local variables, in-memory caches, and files written to disk during one invocation may vanish before the next one runs, sometimes even mid-session.

Anything that needs to persist has to live in an external store:

  • a session → a database or KV store
  • a counter → a database, not a global variable
  • a cache → a managed cache service, not process memory
  • an uploaded file → object storage, not the local filesystem

This is the single biggest mental shift for a developer used to a traditional server that keeps running (and remembering) between requests.

Cold starts

The first time a function runs, or the first time in a while, the platform has to prepare an execution environment before your code can run:

  1. Boot a container or micro-VM.
  2. Load the runtime (Node.js, Python, whatever the function needs).
  3. Initialize your dependencies (imports, DB clients, SDKs).

That preparation time gets added on top of your function's own execution time, and it shows up as a slow first request. How bad this is depends entirely on the isolation model the platform uses.

Containers and micro-VMs vs V8 isolates

This is the real technical split between "classic" serverless (AWS Lambda, Google Cloud Functions) and "edge" serverless (Cloudflare Workers).

AWS Lambda runs each function inside a Firecracker micro-VM: a lightweight virtual machine with its own kernel boundary. This gives very strong isolation, but booting a VM, even a fast one, takes real time (typically 100ms to a few seconds depending on runtime and package size). AWS keeps warm instances around to hide this for busy functions, but a function that has not run in a while pays the cold-start tax on the next request.

Cloudflare Workers runs each piece of code inside a V8 isolate, the same lightweight sandboxing mechanism that keeps browser tabs separate from each other. Isolates share one already-running process and boot in low single-digit milliseconds, because there is no OS and no kernel to start, just a fresh JavaScript execution context. This is why Workers effectively has no meaningful cold start: your code is one of thousands of isolates already multiplexed onto a process running on the edge server closest to the user.

Vercel and Netlify functions are a hybrid:

  • Their standard "serverless functions" run on AWS Lambda underneath, so they inherit Lambda's cold-start profile.
  • Their "edge functions" run on something closer to the Workers isolate model, trading some Node.js compatibility for near-zero cold starts.

CPU time vs wall-clock time

This distinction explains why Cloudflare's "10ms" limit is not as brutal as it sounds, and also why it can be brutal.

  • Workers bills and limits CPU time: the time your code is actually executing on the processor.
  • Lambda and Cloud Functions bill (and often limit) wall-clock duration and memory allocated.

A function that calls an external API and waits 300ms for a response uses close to 0ms of CPU while it waits; that waiting time is free on Workers. But a function that does real computation (resizing an image, parsing a huge JSON payload, hashing in a loop) burns CPU time fast, and 10ms disappears in a hurry.

Lambda's model is more forgiving for I/O-heavy work in terms of hitting a hard limit, but more expensive at scale, because it keeps billing for allocated memory the whole time the function is alive, waiting included.

Where serverless bites

PatternWhy it failsWhat to use instead
Long-running jobs (video encoding, big batch exports)Lambda caps at 15 minutes; Workers caps CPU time (free) or 5 minutes wall time (paid)A queue plus a container worker (Cloud Run, ECS), not a request/response function
Websockets, persistent connectionsA serverless function terminates once it returns a responseCloudflare Durable Objects, or API Gateway WebSockets + Lambda + a connection table on AWS
Heavy CPU work (ML inference, image/video processing)Blows through CPU-time limits on Workers, or gets expensive fast on Lambda (billed for allocated memory the whole run)A container platform, or offload to a queue and a purpose-built worker process

Edge network vs region-based deployment

"Serverless" and "edge" are related but not the same thing:

  • A region-based platform (Lambda, Cloud Functions, a standard Vercel serverless function) runs your code in one data center region you pick (for example ap-southeast-1). Every request, no matter where the user is, travels to that region.
  • An edge platform (Cloudflare Workers, Vercel Edge Functions) runs your code in whichever data center is physically closest to the user, out of hundreds of points of presence worldwide, with no region to pick at all.

For a Malaysian SME audience this mostly matters for latency on simple, fast operations (auth checks, redirects, personalization, A/B logic) where shaving 100-200ms of network round-trip is noticeable. It matters far less for anything that has to talk to a single-region database anyway (like a Supabase project pinned to Singapore), because the database round-trip dominates regardless of where the function itself runs.

A quick glossary

  • Invocation: one execution of a function, triggered by one event.
  • Concurrency: how many invocations of the same function run at the same time; platforms cap this to protect shared resources.
  • Warm instance: an already-booted execution environment kept alive briefly to skip the cold start on the next request.
  • Isolate: a lightweight, memory-sandboxed execution context (Workers' unit of isolation, versus a VM or container).
  • Subrequest: an outbound network call (fetch, database query, storage read) made from inside a function; platforms often cap how many one invocation can make.

The storage side

Serverless functions are stateless, so they lean hard on managed storage services sitting next to them. Cloudflare and AWS both offer a family of these, and they map roughly like this:

CloudflareAWS equivalentWhat it actually is
KVDynamoDB (simplified)Global, eventually-consistent key-value store. Fast reads everywhere, writes propagate in seconds. Good for config, feature flags, cached lookups.
R2S3Object storage (files, images, video, backups). R2's headline difference: zero egress fees, where S3 charges for data leaving AWS.
D1RDS / Aurora Serverless (simplified)SQLite-based relational database, run at the edge. Good for small-to-medium relational data close to Workers, not a Postgres replacement for a real app's primary database.
Durable ObjectsNo clean 1:1 match; closest is a mix of DynamoDB + Lambda + SQS, or Azure Durable FunctionsA single-threaded, strongly-consistent stateful object with its own storage, addressable by ID. Used for things that need one authoritative in-memory instance: a chat room, a game session, a rate limiter, a websocket coordinator.

The pattern to notice: Cloudflare's storage products are edge-native and cheap at small scale (R2's free egress in particular is a real cost win), while AWS's are older, deeper, and more configurable, but carry more operational weight and steeper pricing at the margins, especially egress.

The landscape (comparison tables)

Serverless function platforms

PlatformIsolation modelTypical cold startMax execution timeFree tierBest for
AWS LambdaFirecracker micro-VM~100ms to 2s cold, near-0 warm15 minutes1M requests + 400,000 GB-seconds/month, permanentDeep AWS integrations, background jobs, anything already living in AWS
Google Cloud FunctionsgVisor-sandboxed container~100ms to 1s cold9 min (1st gen) / 60 min (2nd gen, Cloud Run-based)~2M invocations/month (varies by region)Event-driven microservices, GCP-native stacks (Firebase, BigQuery triggers)
Vercel FunctionsLambda-based (serverless) or edge runtime (isolate-based)Lambda-like for serverless, near-0 for edge10s default on Hobby, higher on paid plans~100k invocations/month on Hobby, non-commercial use onlyNext.js API routes, anything already deployed on Vercel
Netlify FunctionsLambda-based underneathLambda-like10s default (26s for background functions)125k requests/month, 100 hours run-time/monthStatic-site-first projects, JAMstack apps
Cloudflare WorkersV8 isolateNear-0 (single-digit ms)10ms CPU/request (free) / 30s default, up to 5 min configurable (paid)100,000 requests/day, 10ms CPU/request, 50 subrequests/requestLatency-sensitive edge logic, high-request/low-CPU workloads, global reach without picking a region

Storage: Cloudflare vs AWS

NeedCloudflareAWSNotes
Simple fast key-value lookupsKVDynamoDBKV is simpler and cheaper at small scale; DynamoDB is more powerful (transactions, indexes) at large scale
File/object/video storageR2S3R2 has zero egress fees; a real cost decision for media-heavy apps
Small relational data at the edgeD1Aurora Serverless / RDSD1 is SQLite-based and edge-local, not a substitute for a real primary Postgres database
Stateful coordination (chat, game state, rate limits)Durable ObjectsDynamoDB + Lambda + SQS (assembled by hand)Durable Objects gives one thing AWS does not offer natively: a single strongly-consistent stateful actor per ID

How to choose (decision rules)

  • If the work is I/O-bound (calling Supabase, calling an AI API, sending an email, reading/writing KV or R2), then Cloudflare Workers or Vercel functions are both fine; pick whichever platform is already hosting the rest of the app.
  • If the work is CPU-heavy (image resizing, PDF generation, video transcoding, large in-memory parsing), then avoid the Workers free plan entirely; either upgrade to Workers paid with a raised CPU limit, or move the job to a container platform (Google Cloud Run) or a queue plus a dedicated worker.
  • If the feature needs a persistent connection (websockets, live multiplayer, a live chat room), then classic request/response serverless functions are the wrong tool; use Durable Objects on Cloudflare, or accept the extra AWS plumbing if already on AWS.
  • If it is a background/scheduled job with no user waiting on the response (nightly report, data sync, cleanup), then Lambda, Cloud Functions, or a Cloudflare Cron Trigger are all fine, since none of them need to hide a cold start from anyone.
  • If cold-start latency directly affects a user-facing request (first paint, an API call the UI is blocking on), then prefer Cloudflare Workers or Vercel Edge Functions (isolate-based, near-zero cold start) over Lambda-based functions.
  • If the app is already a Next.js app on Vercel, then default to Vercel functions or Edge functions; only reach for a separate Cloudflare Worker when hitting a Vercel free-tier ceiling or needing a Cloudflare-specific product (KV, R2, Durable Objects).
  • If the data is relational and matters to the whole app (users, orders, bookings), then use Supabase Postgres, not D1; D1 is for small edge-local datasets, not a primary application database.
  • If storing files, images, or video that get downloaded often, then default to R2 over S3, purely on egress cost.
  • If a function needs to hold state between requests inside one logical session (a live auction, a turn-based game, a rate limiter shared across requests), then use Durable Objects rather than trying to fake it with KV, which is only eventually consistent.

Univa playbook

Univa's actual footprint today:

  • univa.my runs on Cloudflare Pages (static site) with Cloudflare Workers for dynamic logic, deployed by GitHub Actions and wrangler.
  • Client apps built on the Next.js + Supabase + Vercel stack (per docs/app-building-philosophy.md) run their backend logic as Vercel serverless or edge functions, with Supabase Postgres handling all real data.
  • AWS Lambda and Google Cloud Functions rarely enter the picture unless a client already has infrastructure sitting in AWS or GCP.

The lesson already learned in production: do not burst-probe a Cloudflare Worker, and budget CPU time before writing anything heavier than a simple pass-through or a light transform into a free-plan Worker. If a feature needs real compute (PDF generation for an invoice tool, image processing for a review-responder, batch AI calls), either:

  1. Put it behind a Vercel function, which is more forgiving on CPU/runtime for this kind of work under Node.js, or
  2. Plan for the Workers paid tier ($5/month, configurable CPU up to 5 minutes) before shipping it.

Default storage picks for new Univa builds:

  • R2 for any file, image, or video storage. Zero egress beats S3 outright for a cost-conscious build.
  • Supabase Postgres for anything relational and app-critical.
  • KV only for small, hot config or cache values that do not need strong consistency.
  • Durable Objects reserved for the rare case of building something genuinely realtime and stateful (a live queue system, a live scoreboard), not reached for by default.

Common pitfalls to watch for on future builds:

  • Writing a Worker that assumes it can keep a counter or cache in a module-level variable across requests; it cannot be relied on to survive.
  • Forgetting that Workers free plan subrequests cap at 50 per invocation, which matters if a function fans out to several APIs or storage calls at once.
  • Choosing D1 as a primary database out of convenience, then hitting its limits once the app needs real relational features Supabase already handles well.
  • Assuming "serverless" means "infinitely scalable for free"; every platform here has a concurrency or rate ceiling somewhere, free or paid.

Hands-on exercise

  1. Install wrangler and scaffold a minimal Cloudflare Worker (npm create cloudflare@latest).
  2. Start with a plain "hello world" fetch handler:
export default {
  async fetch(request) {
    return new Response("hello from the edge");
  }
};
  1. Run it locally with wrangler dev, confirm it responds instantly with no visible delay, then deploy it live with wrangler deploy.
  2. Deliberately add a CPU-heavy loop inside the handler:
export default {
  async fetch(request) {
    let hash = 0;
    for (let i = 0; i < 5_000_000; i++) {
      hash = (hash * 31 + i) % 1_000_000_007;
    }
    return new Response(`hash: ${hash}`);
  }
};
  1. Redeploy and watch the Cloudflare dashboard's CPU-time metric for that request. Keep increasing the loop size until the Worker throws error 1102 (CPU time exceeded) on the free plan.
  2. Build the exact same "heavy loop" logic as a Vercel serverless function and compare: does it fail the same way, does it just run slower, or does it succeed because Vercel is billing wall-clock time instead of CPU time?
  3. Write down, in one paragraph, which of the two platforms you would pick for a feature that formats and returns a large report, and why.

Self-check

Q1. Why does a Cloudflare Worker rarely suffer a meaningful cold start, while an AWS Lambda function sometimes does?

Q2. What is the practical difference between a platform that bills/limits CPU time (Workers) versus one that bills/limits wall-clock duration and memory (Lambda), for a function that spends most of its time waiting on a slow external API?

Q3. Name two kinds of workloads that do not belong on a classic serverless function, and say what you would use instead.

Q4. What is the closest Cloudflare equivalent to Amazon S3, and what is the one pricing difference worth caring about?

Q5. A Univa client wants a feature that resizes uploaded images before storing them. Where should that logic run, and why?

Answers:

  • A1. Workers use V8 isolates, a lightweight sandbox that shares an already-running process, so there is no VM or container to boot. Lambda boots a Firecracker micro-VM per cold instance, which takes real time.
  • A2. A mostly-waiting function stays cheap and safe on CPU time (Workers), because waiting is nearly free. The same function on Lambda still gets billed for the full duration and memory allocation, which costs more even though the CPU is idle.
  • A3. Long-running jobs (video encoding, batch processing) need a container or queue-based worker, not a serverless function. Persistent connections (websockets, live chat) need Durable Objects or a dedicated connection-tracking setup, not request/response functions.
  • A4. R2 is the closest equivalent to S3. The one difference worth caring about is R2's zero egress fee versus S3 charging for data leaving AWS.
  • A5. Not a free-plan Cloudflare Worker: image resizing is CPU-heavy and will blow the 10ms limit fast. Run it as a Vercel serverless function, or a dedicated Cloud Run container for heavier volumes, where wall-clock/memory billing tolerates the work better.

Further reading