Blog
·12 min

Edge Computing e Architettura Serverless: Guida per Sviluppatori

Una guida tecnica approfondita che copre piattaforme di edge computing, modelli di funzioni serverless, ottimizzazione del cold start, pattern di gestione dello stato, accesso ai database da serverless e quando scegliere serverless rispetto all'infrastruttura tradizionale.

The way we deploy software has undergone a quiet revolution. Ten years ago, deploying meant provisioning a virtual machine, installing a runtime, configuring a reverse proxy, and hoping the monitoring caught problems before users did. Today, you can push a function to a serverless platform and have it serving requests at global scale within seconds, without ever thinking about an operating system. Edge computing extends this further: your code runs not in a single region but in dozens of data centers distributed across the planet, minutes from every user.

This shift from servers to functions and from regions to the edge is not just a change in operations. It is a fundamental change in how you architect applications. Caching strategies change. Database access patterns change. Deployment workflows change. The mental model of a constantly running process that holds state in memory is replaced by one of ephemeral invocations that must be stateless, fast to start, and designed for a world where every request could land on a different machine.

This guide covers the practical reality of building applications with edge compute and serverless functions. It covers the platforms, the tradeoffs, the optimization strategies that actually matter in production, and — just as importantly — the situations where you should not use any of this and should stick with a long-running server.

Understanding serverless compute models

Serverless is a misnomer. There are servers — plenty of them — but you do not manage them. The cloud provider handles provisioning, scaling, patching, and capacity planning. You provide code, and the provider runs it on demand. Beyond this high-level definition, two distinct models have emerged that serve very different purposes.

Functions as a Service (FaaS)

FaaS is what most developers picture when they hear serverless. You write a single-purpose function, deploy it to a platform like AWS Lambda, Cloudflare Workers, or Google Cloud Functions, and the platform invokes it in response to events — HTTP requests, queue messages, database change streams, or scheduled timers. Each invocation runs in an isolated sandbox, and you pay only for the compute time your function consumes, rounded up to the nearest millisecond or hundred-millisecond increment depending on the provider.

FaaS works well for request-response workloads, webhook handlers, image processing pipelines, and any task that can be expressed as a stateless transformation of an input to an output. The constraint is execution duration: Lambda functions timeout after fifteen minutes, Workers after thirty seconds (or fifteen minutes for paid plans using Durable Objects), and Cloud Functions after nine minutes for HTTP-triggered functions and sixty minutes for background functions. These limits shape what you can build.

Backend as a Service (BaaS)

BaaS takes a different approach. Instead of running your code, the provider runs managed services that you integrate via SDKs and APIs. Firebase, Supabase, Auth0, and Neon are BaaS offerings. You get authentication, databases, file storage, and realtime subscriptions without deploying anything. BaaS and FaaS often complement each other: your functions use BaaS services for persistence and auth, and the BaaS services trigger your functions when data changes.

The key difference is architectural. With FaaS you control the logic; with BaaS you configure the logic. BaaS is faster to build with but harder to escape from. Once your authentication is tied to Firebase and your file storage to S3, migrating becomes a multi-month project. This is not necessarily a bad tradeoff, but it is one you should make consciously rather than by accident.

Edge computing platforms and use cases

Edge computing moves computation to locations that are geographically close to users. Instead of routing all requests to a single us-east-1 data center, edge platforms run your code in dozens or hundreds of locations worldwide. The latency difference is dramatic: a user in Tokyo connecting to us-east-1 sees roughly 150 milliseconds of network latency; connecting to a Tokyo edge node sees under five milliseconds.

Three platforms dominate the edge compute space, each with a different architecture and set of tradeoffs.

Cloudflare Workers

Cloudflare Workers run on the V8 Isolates runtime, not containers. Each Worker runs in a JavaScript isolate — a lightweight sandbox that starts in microseconds rather than the tens or hundreds of milliseconds a container needs. This design decision has profound implications: Workers can handle millions of requests per second across 330+ data centers, cold starts are effectively eliminated, and the pricing is based on requests rather than compute duration. The tradeoff is that Workers run in a limited runtime environment. You cannot run arbitrary binaries, the CPU time per request is capped, and there is no filesystem access in the default tier.

Workers excel at request modification, API composition, authentication and authorization checks, A/B testing, and serving as an orchestration layer in front of your origin servers. They are less suitable for CPU-intensive computation or workloads that exceed the 30-second CPU time limit.

AWS Lambda@Edge and CloudFront Functions

AWS offers two edge compute tiers integrated with CloudFront. CloudFront Functions run at the edge locations and handle lightweight request and response transformations. They have a 10-microsecond billing increment, a 2 MB memory limit, and a 100-microsecond execution limit. They are designed for URL rewrites, header modifications, and cache key manipulation — tasks that must complete before CloudFront decides to hit the cache or forward to the origin.

Lambda@Edge runs in the same CloudFront edge locations but provides the full Lambda environment with support for Node.js, Python, and custom runtimes via the Lambda Execution Environment. It has a five-second execution limit for viewer-request and viewer-response triggers and thirty seconds for origin-request and origin-response triggers. Lambda@Edge can read from DynamoDB, call external APIs, and generate dynamic content at the edge, making it suitable for personalization, geolocation-based content, and bot detection.

Other edge platforms

Fastly Compute@Edge uses a WebAssembly-based runtime that supports Rust, JavaScript (via Javy), and C. It offers sub-millisecond cold starts and predictable performance because WASM compilation happens at deploy time rather than request time. Deno Deploy and Vercel Edge Functions also run on V8 isolates, similar to Cloudflare Workers, and integrate tightly with their respective platform ecosystems.

  • Cloudflare Workers: Best for HTTP orchestration, authentication, and API composition at global scale with sub-millisecond cold starts.
  • Lambda@Edge: Best when you need full Lambda capabilities at the edge with access to the AWS ecosystem including DynamoDB and S3.
  • Fastly Compute@Edge: Best for WASM-based workloads that require predictable performance and polyglot runtime support.
  • Vercel Edge Functions: Best when you are already in the Vercel ecosystem and need edge compute tightly integrated with your frontend framework.
  • Deno Deploy: Best for TypeScript-native developers who want a simple deploy experience with global distribution.

Primary use cases for edge compute

  • Dynamic content personalization at the edge without forcing a cache miss or round-tripping to a central origin.
  • API gateway functionality — authentication, rate limiting, request validation, and response transformation — distributed across all edge locations.
  • A/B testing and feature flag evaluation where the flag check happens before the request reaches the origin server.
  • Geolocation-based routing and content delivery where users in different regions see region-specific content without additional latency.
  • Bot detection and request filtering at the network edge, blocking malicious traffic before it reaches application servers.
  • Server-side rendering of HTML at the edge to deliver fast initial page loads with full SEO support.
Move logic to the edge when reducing latency for end users matters more than reducing complexity for your team. Edge compute is not free — the distributed debugging, state management, and deployment coordination all add overhead. Only pay that cost when your users actually feel the difference.

Cold start optimization strategies

Cold starts remain the most discussed drawback of serverless computing. When a function is invoked after being idle, the platform must provision a new sandbox, load your code, and run initialization logic before it can handle the request. This adds latency — typically between 200 milliseconds and several seconds depending on the runtime and the size of your deployment package.

The severity of cold starts varies dramatically by platform and runtime. Cloudflare Workers effectively eliminated the problem by using V8 isolates instead of containers — cold starts take microseconds. Lambda with Node.js restores from a snapshot in the SnapStart feature, reducing cold starts to under 200 milliseconds. Lambda with Java or .NET without optimization can take several seconds. Understanding these differences is the first step to mitigating the problem.

Reduce deployment package size

The most impactful optimization is shrinking your deployment package. Cold start time correlates strongly with the size of the code that must be loaded into memory. Strip unnecessary dependencies, use tree-shaking to remove unused exports, and consider using a bundler like esbuild or tsup to produce a single minified file. A Lambda function deployed as a 500 KB bundle starts significantly faster than one deployed as a 50 MB bundle, even if both use the same runtime.

// Before: lambda handler with the entire SDK bundled
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { S3Client } from "@aws-sdk/client-s3";
import { LambdaClient } from "@aws-sdk/client-lambda";

// After: only import what you actually use
import { DynamoDB } from "@aws-sdk/client-dynamodb";

const client = new DynamoDB({ region: process.env.AWS_REGION });

export const handler = async (event: APIGatewayProxyEvent) => {
  const result = await client.getItem({
    TableName: process.env.TABLE_NAME!,
    Key: { pk: { S: event.pathParameters!.id } },
  });
  return {
    statusCode: 200,
    body: JSON.stringify({ item: result.Item }),
    headers: { "content-type": "application/json" },
  };
};

Lazy initialization and connection reuse

The second major optimization is moving expensive initialization out of the request handler and into the global scope. In Lambda, code executed outside the handler runs once when the sandbox is initialized and persists across warm invocations. Database connections, HTTP clients, and configuration loaders should be initialized at module scope, not inside the handler function.

// Bad: creates a new connection on every invocation
import { PrismaClient } from "@prisma/client";

export const handler = async () => {
  const prisma = new PrismaClient();
  const result = await prisma.user.findMany();
  await prisma.$disconnect();
  return result;
};

// Good: reuses the connection across warm invocations
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export const handler = async () => {
  return prisma.user.findMany();
};

There is a subtlety here. The AWS SDK v3 for JavaScript includes middleware that retries requests and handles credentials. Creating a single client at module scope means all invocations within a sandbox share the same connection pool and SSL session. This reduces latency by 50-100 milliseconds per invocation after the first one. The same principle applies to HTTP clients, Redis connections, and any other expensive resource.

Provisioned concurrency and keep-warm patterns

For functions that cannot tolerate cold starts, provisioned concurrency keeps a specified number of sandboxes warm and ready. AWS Lambda provisioned concurrency keeps execution environments initialized and ready to handle requests instantly. The cost is equivalent to running those instances continuously, which erodes one of serverless main advantages — paying only for what you use.

A more cost-effective alternative for some workloads is the keep-warm pattern: a CloudWatch Event that invokes your function every few minutes to prevent the sandbox from being recycled. Lambda reclaims idle execution environments after roughly fifteen minutes, so a ping every five minutes keeps a few environments warm at minimal cost. The caveat is that this works only for functions with predictable traffic patterns. If your traffic spikes from zero to thousands of requests per second, the pings will not keep enough environments warm to absorb the burst.

State management patterns for serverless

Serverless functions are stateless by design. Two successive invocations from the same user may run in different sandboxes on different machines. Local filesystem writes disappear. In-memory data is lost. Building reliable applications on this foundation requires deliberately managing state outside the function runtime.

External state stores

The simplest and most reliable approach is pushing state to an external service. Use DynamoDB, Redis (via ElastiCache or Upstash), or a relational database for persistent state. Use S3 or Cloudflare R2 for file storage and large objects. The function reads and writes state through SDK calls, making every invocation idempotent and independent of any particular sandbox.

The cost is latency. Every function invocation that needs state must make at least one network round trip to the state store. For Lambda functions deployed in a single region, a DynamoDB read takes 5-15 milliseconds. For edge functions connecting to a centralized database in us-east-1 from an edge node in India, that same read takes 200-300 milliseconds. This is the central tension in edge computing: the edge gives you fast compute close to the user, but your data still lives somewhere, and getting it to the edge costs time.

Durable Objects and D1 (Cloudflare)

Cloudflare offers a unique solution to the state problem with Durable Objects. A Durable Object is a single-instance JavaScript class that lives in a specific data center and maintains in-memory state across requests. Multiple Workers can send messages to the same Durable Object, and it processes them in a consistent order. This gives you strongly consistent state at the edge without sacrificing the global distribution of compute.

  • Use Durable Objects for coordination state: WebSocket connection management, rate limit counters, distributed locks, and real-time multiplayer game state.
  • Use D1 (Cloudflare serverless SQLite) for relational data that benefits from edge distribution with read replication across regions.
  • Use KV (Cloudflare key-value store) for configuration, feature flags, and cached data that does not require strong consistency.
  • Use R2 for object storage that should be accessible globally without egress fees.

Function orchestration with Step Functions and Workflows

Complex business processes often span multiple functions. A user registration flow might validate input in one function, create a database record in a second, send a welcome email in a third, and update a search index in a fourth. Coordinating these steps reliably — with retries, error handling, and observability — requires orchestration.

AWS Step Functions lets you define workflows as state machines with JSON. Each state can be a Lambda invocation, a choice branch, a parallel execution, or a wait step. The state machine itself stores the workflow state durably, so even if every Lambda function fails simultaneously, the workflow resumes from the last completed step. Cloudflare offers a similar capability through Queues and Durable Object-based workflows.

The hardest lesson in serverless development is that stateless functions require extremely disciplined state management. Every time you reach for a global variable or a local file in a function, you are introducing a bug that will only manifest under load. Externalize state early, and your production debugging sessions will be dramatically shorter.

Database access from edge and serverless

Database access is the most contentious topic in serverless architecture. Traditional database drivers assume persistent connections — they open a TCP connection at application startup and reuse it for the lifetime of the process. Serverless functions open and close connections rapidly, and the overhead of establishing a new TLS connection for every invocation can dominate the total execution time.

Connection pooling and serverless drivers

The first generation of serverless database access used connection pooling layers like PgBouncer or RDS Proxy. These services sit between your function and the database, maintaining a pool of persistent connections and multiplexing ephemeral function connections across them. RDS Proxy reduces the connection overhead from hundreds of milliseconds to single digits while also protecting the database from connection storms when functions scale up rapidly.

The second generation is purpose-built serverless database drivers that use HTTP or WebSocket transports instead of TCP sockets. Neon uses a WebSocket-based driver that avoids TCP handshake overhead entirely. PlanetScale offers a MySQL-compatible HTTP API. Turso uses libsql with HTTP and WebSocket support. These drivers connect within milliseconds, making them suitable for Lambda functions with sub-second execution times.

import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";

const sql = neon(process.env.DATABASE_URL!);
const db = drizzle(sql);

export async function handler(request: Request) {
  const users = await db.query.users.findMany({
    where: (users, { gte }) => gte(users.lastLogin, new Date("2026-01-01")),
    limit: 50,
  });

  return new Response(JSON.stringify(users), {
    headers: { "content-type": "application/json" },
  });
}

Query optimization for serverless latency budgets

Every database query in a serverless function competes for a limited execution budget. A function that takes 500 milliseconds to run costs more and feels slower than one that takes 50 milliseconds. This shifts the optimization focus from reducing server costs to reducing function duration. The result is that database queries that were acceptable in a server context — three or four sequential queries to assemble a response — become unacceptable in serverless because each query adds a network round trip.

The solution is to combine queries aggressively. Use JOINs instead of separate lookups. Use GraphQL DataLoader or Dataloader patterns to batch queries within a single invocation. Use read replicas or cached materialized views for queries that do not need real-time consistency. Every eliminated round trip is a direct reduction in function execution time and therefore in cost.

Edge database tradeoffs

Edge functions that query a centralized database in us-east-1 face 100-300 milliseconds of network latency per query. This negates much of the benefit of edge compute. The solution is either to replicate data to the edge or to use a distributed SQLite-based database like D1 or Turso that stores data closer to the edge locations.

Distributed databases present their own tradeoffs. D1 uses SQLite, which handles concurrent writes through a single-writer model — acceptable for read-heavy workloads but limiting for write-heavy applications. Turso uses embedded replicas for reads and a primary for writes, with eventual consistency for read replicas. You must decide whether strong consistency is required for your use case. If it is, you may need to accept the latency of reaching a centralized primary database from the edge.

Observability and debugging

Debugging serverless functions is harder than debugging servers because the runtime is ephemeral. You cannot SSH into a function to check its state. You cannot attach a debugger to a running invocation. You cannot tail logs from a specific instance because instances are created and destroyed dynamically. Effective observability requires a fundamentally different approach.

Structured logging and distributed tracing

Console.log statements scatter across CloudWatch Logs are not enough for production debugging. Every function invocation should emit structured JSON logs that include a correlation ID linking the invocation to upstream and downstream services. AWS Lambda automatically provides the request ID in the context object, and you should propagate this ID through your database queries, external API calls, and downstream function invocations.

Distributed tracing tools like AWS X-Ray, DataDog, and Honeycomb give you visibility into the full lifecycle of a request: how long each function took, which downstream services it called, which database queries it executed, and where time was spent. X-Ray integrates natively with Lambda and Step Functions, providing service maps that visualize the interactions between your functions without any code changes.

Local emulation and testing

Testing serverless functions locally has improved significantly. The AWS SAM CLI, Serverless Framework Offline, and LocalStack provide local emulation of Lambda, API Gateway, DynamoDB, and other services. Cloudflare Workers can be run locally with wrangler dev, which provides hot reload and access to local emulations of KV, D1, R2, and Durable Objects. These tools let you iterate rapidly without deploying to production for every change.

# wrangler.toml for Cloudflare Workers with local development
name = "api-worker"
main = "src/index.ts"
compatibility_date = "2026-06-01"

[[d1_databases]]
binding = "DB"
database_name = "app-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

[[kv_namespaces]]
binding = "KV"
id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Run locally: npx wrangler dev
# Deploy: npx wrangler deploy
If you cannot trace a single request through your entire system — from the edge function that receives it to the database query that serves it — then you do not have observability, you have log noise. Correlation IDs and distributed tracing are not optional for production serverless systems. They are the difference between a five-minute debugging session and a five-hour one.

Cost considerations and scaling

Serverless pricing is deceptive. The per-invocation cost on paper is tiny — Lambda charges $0.20 per million requests plus $0.0000166667 per GB-second of compute time. These numbers look impossibly cheap. In practice, costs can surprise teams that do not understand how serverless billing interacts with real-world traffic patterns.

Understanding the cost drivers

Three factors drive serverless costs: invocation count, execution duration, and memory allocation. Doubling the memory allocation doubles the per-second compute cost but may halve the execution duration, potentially leaving the total cost unchanged or even reduced. Finding the optimal memory allocation for each function requires experimentation: benchmark your function at 256 MB, 512 MB, 1024 MB, and 2048 MB, and compute the cost per invocation at each level.

Data transfer costs are the hidden tax. Lambda functions that communicate across regions — an edge function calling a centralized database in another region — incur data transfer charges that can exceed the compute costs by an order of magnitude. Cloudflare Workers do not charge for egress, but Lambda@Edge and Vercel Edge Functions do. Model your data flow before committing to an architecture, and use Cloudflare R2 or CloudFront to cache data at the edge and reduce cross-region traffic.

Scaling behavior and throttling

Serverless scales automatically, but it does not scale infinitely. Lambda has a regional concurrency limit (typically 1000 concurrent executions by default, adjustable via support ticket). Cloudflare Workers have a per-plan request limit. If a traffic spike exceeds your concurrency limit, requests are throttled with a 429 or 503 response. The solution is to request limit increases proactively, implement queue-based load leveling for background tasks, and use provisioned concurrency for latency-sensitive functions.

The sweet spot for serverless is workloads with variable traffic and low-to-medium throughput. A function serving 10 requests per second costs pennies per month. A function serving 10,000 requests per second costs thousands. At some point — typically around 50,000 requests per second — a fixed server infrastructure becomes more cost-effective. The crossover point depends on your function complexity, memory allocation, and execution duration.

When NOT to use serverless

Serverless and edge computing are powerful tools, but they are not the right tool for every job. Recognizing the situations where serverless adds cost and complexity without commensurate benefit is a mark of engineering maturity.

Long-running or stateful workloads

WebSocket servers, real-time video processing pipelines, and machine learning model inference with large models all require execution times or resource allocations that exceed serverless limits. Lambda functions cannot run for more than fifteen minutes. Cloudflare Workers have a thirty-second CPU time limit. If your workload needs to hold a persistent connection, maintain in-memory state across long periods, or run for hours, a traditional server or a container-based service is the correct choice.

Predictable high-throughput traffic

If your traffic is steady and predictable — a B2B API serving the same 500 requests per second twenty-four hours a day — serverless costs more than a fixed server. A t3.medium EC2 instance costs roughly $30 per month and can handle thousands of requests per second for a well-optimized Node.js service. The same workload on Lambda would cost several hundred dollars per month due to per-request overhead and the premium for auto-scaling capability you do not need.

  • Avoid serverless for steady-state, high-throughput workloads where the auto-scaling benefit is unused but the per-request pricing premium is paid.
  • Avoid serverless for workloads that require GPUs, large memory allocations (>10 GB), or access to specialized hardware.
  • Avoid serverless for applications that maintain long-lived connections to databases or message queues, as the connection churn degrades performance and increases costs.
  • Avoid serverless for latency-critical real-time systems where cold start jitter is unacceptable and provisioned concurrency costs exceed dedicated infrastructure.
  • Avoid serverless when your team lacks the operational tooling for distributed debugging, structured logging, and tracing — debugging serverless without these tools is painful.

Serverless as a forced abstraction

Some teams adopt serverless because it is the modern choice or because a platform vendor makes it frictionless. These are the wrong reasons. Serverless is an architecture with specific tradeoffs: you trade operational control for operational simplicity, you trade consistent latency for auto-scaling, and you trade the ability to reason about your system as a single process for the ability to deploy independent functions. If the tradeoffs do not benefit your specific use case, a container running on a VM with a simple systemd unit file is a better solution.

The most practical approach for most teams is hybrid. Use serverless functions for the request-response API layer where traffic is variable and auto-scaling matters. Use containers or servers for the background processing layer where jobs run for minutes or hours. Use managed databases (RDS, Neon, Supabase) regardless of the compute model, because managing your own database in production is rarely worth the effort. The hybrid approach lets you match the compute model to the workload rather than forcing every workload through a single abstraction.

Conclusion and recommendations

Edge computing and serverless architecture represent a genuine advance in how we build and deploy software. The ability to run code in dozens of data centers worldwide, to scale from zero to thousands of requests per second without provisioning, and to pay only for the compute you consume has changed what is possible for independent developers and enterprise teams alike.

The key to using these technologies effectively is understanding their constraints as deeply as you understand their advantages. Stateless functions require disciplined state management. Fast cold starts require careful optimization of deployment packages and initialization code. Distributed debugging requires structured logging and tracing from day one. Database access from the edge requires rethinking query patterns and data locality. None of these constraints is a dealbreaker, but each one must be accounted for in the architecture.

Start with a clear picture of your workload profile. Measure the latency your users actually experience, not the latency your function reports in isolation. Model the costs at realistic traffic levels, not at the per-invocation price on the pricing page. Build observability into every function from the first commit. And always ask yourself whether the complexity you are adding is justified by the problem you are solving.

For most teams building new applications in 2026, the default starting point should be a serverless API layer (Cloudflare Workers or Lambda with API Gateway) backed by a managed database (Neon or Supabase), with a container-based job runner for background tasks. This combination provides the global distribution and auto-scaling of serverless for user-facing requests while avoiding the pain points of serverless for the workloads that do not fit. As your application grows, you can migrate individual components to more specialized infrastructure — but starting simple and adding complexity only when the data justifies it remains the best strategy in every architectural paradigm.