#DevOps

Serverless Architecture in Practice

Serverless lets you focus on code instead of servers, but its stateless design, cold starts, and billing model impose new constraints. This article explains its FaaS + BaaS core, where it fits (event-driven, bursty, and low-traffic workloads), its costs (cold starts, vendor lock-in, and difficult debugging), and when to use or avoid it.

Key Points

  • Topic Focus: DevOps
  • Suitable for: indie hackers, full-stack engineers, AI application developers
  • Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Author @admin·Published July 26, 2026·Updated July 26, 2026

I helped a startup move an internal tool from ECS to Lambda. On deployment day, the founder looked at the bill and exclaimed, "What a deal!" The cost fell from an always-on instance at RMB 800 per month to RMB 30 per month billed by invocation. Two weeks after the workload went live, the real test began: the first request was always two or three seconds slower, the database connection count mysteriously exploded, and an intermittent production bug could never be reproduced locally. This article explains Serverless's core model, benefits, and costs in one place, so you know where the traps are before you jump in.

1. What Serverless Really Is: FaaS + BaaS

Many people equate Serverless with "cloud functions," but that is only half the picture. Serverless combines two layers:

  • FaaS (Function as a Service): You write a function, and the cloud provider runs it, scales it, and meters it. AWS Lambda, Vercel Functions, Cloudflare Workers, and Alibaba Cloud Function Compute all fall into this category. You never touch a server or even a process; you only handle "event arrives → function runs → response returns."
  • BaaS (Backend as a Service): Backend capabilities such as databases, object storage, authentication, and message queues become managed APIs. Supabase, Firebase, DynamoDB, S3, and Cognito are all BaaS products.

In a truly "serverless" system, FaaS runs your code while BaaS handles state and storage. Both bill by usage and scale automatically. The essential change is simple: you no longer pay for "machines"; you pay for "execution." When there is no traffic, no function instance is running and billing falls to zero. That is the biggest difference between Serverless and a traditional cloud server.

In one sentence: Serverless does not mean there are no servers; it means servers are no longer your concern.

2. Its Real Advantages: No Server Operations, Usage-Based Billing, and Automatic Scaling

Serverless is not a silver bullet, but it solves three very real problems:

  1. No server operations: There are no machines to patch, no operating systems to upgrade, and no 3 a.m. emergencies because SSH will not connect. Security updates, capacity planning, and availability-zone distribution are all the cloud provider's responsibility. For a team of three to five people, that can save the salary of a dedicated SRE.
  2. Usage-based billing: Traditional instances run 24×7 and keep burning money even when there is no traffic at midnight. Serverless charges by "request count × execution duration × memory," so no traffic means no cost. Moving a back-office API that receives only a few dozen visits per day can reduce the bill by an order of magnitude.
  3. Automatic scaling, including scale-to-zero: When traffic rises from 1 RPS to 10,000 RPS, the cloud provider automatically starts concurrent function instances. When traffic falls, those instances are reclaimed automatically. You do not need to write an HPA, reserve a buffer, or buy machines in advance "just in case it takes off."

Together, these advantages make Serverless exceptionally cost-effective for low-traffic, bursty, and event-driven workloads.

3. The Costs: Cold Starts, Duration Limits, Statelessness, Lock-In, and Difficult Debugging

The other side of those advantages is five hard constraints, any one of which can make your life difficult:

  • Cold starts: After a function has been idle for a while, its instance is reclaimed. The next request must pull the image again, start the runtime, and load the code. Node/Python usually takes a few hundred milliseconds, while Java/JVM can easily take several seconds. This is the root cause of the "first request is two or three seconds slower" problem from the opening. Cold starts are Serverless's original sin. There are only a few ways to optimize them: trim dependencies, reduce bundle size, prewarm with provisioned concurrency, and choose a lighter runtime.
  • Execution duration limits: A Lambda invocation can run for at most 15 minutes, while Vercel defaults to 10 seconds (which can be raised to 5 minutes, with streaming up to an 800K context). Long-running work simply does not fit inside one function. Video transcoding and large-scale data processing must be partitioned or moved to Step Functions / a queue + worker.
  • Statelessness: Each invocation may land on a different instance. Global variables, in-memory caches, and local files inside a function cannot be trusted across requests. State must move to Redis, a DB, or object storage. Developers accustomed to using const cache = new Map() in a resident process will repeatedly get burned here.
  • Vendor lock-in: Moving from Lambda to Cloudflare involves more than changing an SDK. Triggers, IAM, event formats, and deployment tools are all vendor-specific. Once you adopt Serverless, expect switching providers to be expensive. Cleanly isolating business logic from vendor SDKs is the only way to mitigate this.
  • Difficult debugging and local development: Tools such as serverless-offline can only provide a rough simulation. IAM permissions, interactions among triggers, and network configuration are nearly impossible to reproduce locally. Production troubleshooting depends on CloudWatch / logs, and visibility collapses as the call chain grows. Distributed tracing is not optional in Serverless; it is essential.

4. When to Use It: Events, Bursts, Infrequent Work, and API Backends

The costs are now clear, but Serverless is almost the default choice for these scenarios:

  • Event-driven workloads: Webhook callbacks, generating thumbnails after an S3 upload, triggering a build after a CodePush release, or recording a transaction after a Stripe payment. Run when the event arrives, then disappear when the work is done. That is FaaS's natural form. Keeping a resident service waiting for an event that happens once an hour is wasteful.
  • Scheduled tasks: Run a data aggregation once every night or clean up expired sessions every hour. Triggering a function with cron is far cleaner than maintaining a dedicated cron machine.
  • Bursty traffic: Flash sales, marketing campaigns, or traffic driven by a viral post. Serverless scales automatically to absorb instantaneous peaks, then scales to zero when the event ends. There is no need to buy a full day of machines for five minutes of traffic.
  • Infrequent, low-traffic workloads: Internal back offices, administration tools, and Demo environments. With only a few visits per day, invocation-based billing can be an order of magnitude cheaper than an always-on instance.
  • API backends: With full-stack frameworks such as Next.js / SvelteKit, Vercel / Netlify compiles each API route into a function, giving you a static frontend and a function-based backend in one stack.

5. When Not to Use It: Long-Lived Connections, Resident Services, Strict Latency, and Heavy Compute

Conversely, using Serverless for these scenarios is asking for trouble:

  • Long-lived connections (WebSocket / SSE / gRPC streaming): Functions use a request-response model and exit when the invocation finishes, so they cannot sustain long-lived connections. For real-time delivery, use a dedicated managed service (API Gateway WebSocket, Pusher, or Ably) or run an always-on service.
  • Resident services: A worker consuming Kafka, a listener subscribed to Redis pub/sub, or a chat gateway maintaining in-memory state must keep its process alive. That fundamentally conflicts with a function model that exits when its work is done.
  • Highly predictable, low latency: Cold-start jitter ranging from hundreds of milliseconds to several seconds is unacceptable in a core transaction system with a P99 SLA of 50ms.
  • Heavy compute: Machine-learning training, large-scale video transcoding, and batch ETL. Duration limits and the billing model can make these jobs more expensive than renting GPU instances, and they may not finish at all.

The rule is simple: must this service stay online continuously? If so, do not use Serverless.

6. Database Connections: The Classic Serverless Failure Mode

Almost every team migrating to Serverless encounters this problem, so it deserves its own section.

A traditional application has one resident process holding a connection pool, perhaps with 20 connections, and reuses it indefinitely. Serverless is different: each function instance is an independent, short-lived process. When concurrency arrives, the cloud provider can instantly start hundreds of instances, each opening a new DB connection. A process-based database such as PostgreSQL can exhaust its memory at 200 connections and immediately hit max_connections. The symptoms are FATAL: too many connections, connection timeouts, and database OOM errors.

There are two solutions:

text
Approach A: Add connection-pool middleware in front of the DB (PgBouncer)
  [ Lambda ×N ] ──> [ PgBouncer (transaction mode) ] ──> [ PostgreSQL ]
                       Reuse a small number of connections to absorb burst concurrency

Approach B: Use a native Serverless database
  [ Function ] ──> [ Neon / PlanetScale / Aurora Serverless ]
                    HTTP API or built-in connection pooling, billed by storage and compute
  • Approach A: Put PgBouncer in front of self-hosted PG and enable transaction pooling, turning hundreds of short-lived connections into a dozen or so long-lived connections to the DB. Be aware that transaction mode does not support prepared statements or session-level advisory locks, and Prisma must enable ?pgbouncer=true.
  • Approach B: Use a database designed for Serverless, such as Neon, PlanetScale, or Aurora Serverless v2. These products either provide an HTTP interface, where a connection is simply an HTTP request, or include built-in pooling. They can also "scale to zero" to save money. This is the simplest option for a new project.

In one sentence: Serverless + a traditional direct DB connection = guaranteed failure. Short-lived functions require DB pooling, or a Serverless-native DB.

7. A Typical Architecture: Functions + Static Frontend + Managed DB

Putting everything together gives you today's most common lightweight modern stack:

text
                    ┌────────────────────────────────────────────────────┐
   User ──── CDN ──▶│  Static frontend                                   │
                    │  (Next.js SSG/SSR on Vercel)                       │
                    └─────────────────────────┬──────────────────────────┘
                                              │ API call (function)
                                              ▼
                    ┌────────────────────────────────────────────────────┐
                    │  Serverless Functions                              │  ◀── webhook / cron trigger
                    │  (authentication, business logic, file processing) │
                    └─────┬──────────────────────────┬───────────────────┘
                          │                          │
              ┌───────────▼────┐          ┌──────────▼──────────┐
              │ Serverless     │          │ Object storage S3   │
              │ DB (Neon)      │          │ / R2                │
              └────────────────┘          └─────────────────────┘

Put the static frontend on a CDN, such as Next.js SSG/ISR deployed to Vercel / Cloudflare Pages. Compile business logic into functions, store data in a managed DB, and put large files in object storage. The entire stack is billed by usage, scales automatically, and scales to zero. An MVP built this way can grow from launch to more than 10,000 monthly active users without infrastructure costs exceeding the price of a cup of coffee. This stack is best suited to independent developers, startups, and internal tools.

8. Do Not Go All-In: A Hybrid Architecture Is the Right Answer

One final point, and the place where I have seen the most teams fail: do not go all-in on Serverless.

Serverless has a clear sweet spot and equally firm boundaries. A serious production system usually uses a hybrid architecture: core transactions, long-lived connections, and heavy compute run as resident services on K8s / ECS / bare metal, while edge, event-driven, bursty, and infrequent work uses Serverless. Consider e-commerce: keep the core order service resident to guarantee low P99 latency and long-lived connections; use functions for image thumbnails, reconciliation emails, scheduled reports, and flash-sale throttling; and place the static frontend on a CDN. Give each tool the job it fits; do not use one hammer on every nail.

To decide which model a component should use, ask three questions: Must it stay online continuously? (Yes → resident service.) Is its traffic bursty? (Yes → Serverless.) Are its latency requirements strict? (Yes → resident service.) Once those three questions are answered, the choice is largely settled.

Summary

  • Serverless = FaaS (runs code) + BaaS (holds state/storage), and the core shift is paying for "execution" rather than "machines." Zero spend with no traffic and automatic scale-to-zero are decisive advantages for infrequent and event-driven workloads.
  • You must accept five hard constraints first: cold starts, execution duration limits, statelessness, vendor lock-in, and difficult debugging. The advantages are not free; they are obtained in exchange for these constraints.
  • The sweet spot is event-driven work (webhooks/cron/file processing), bursty traffic, infrequent low traffic, and API backends. The danger zones are long-lived connections, resident services, strict low latency, and heavy compute. If it "must stay online continuously," do not use Serverless.
  • DB connections are the classic failure mode: short-lived functions can instantly overwhelm a directly connected DB. Put PgBouncer in front for transaction pooling, or switch to a Serverless-native DB such as Neon / PlanetScale.
  • The typical lightweight stack is a static frontend (CDN) + Serverless functions + managed DB + object storage, but production systems should use a hybrid architecture: resident core services and Serverless at the edges, each handling the work it fits.

To close in one sentence: Serverless does not hide servers; it hands you the decision of when servers deserve your attention. Choose the right workload and it is absurdly cheap. Choose the wrong one and it is absurdly expensive and slow.

Related

Related Content