We've all heard it: serverless is the future, it scales automatically, and you only pay for what you use. But after 5+ years building production platforms across e-commerce, trading, and ride-hailing systems, I've learned that when serverless is the wrong choice, the costs and operational pain can exceed any benefit. Let me walk you through the situations where containers or always-on infrastructure makes far more sense.
Steady high traffic can be cheaper on containers
This is the most misunderstood aspect of serverless economics. When your traffic is predictable and constant, serverless becomes expensive quickly. Lambda charges per invocation and per 100ms of execution time. If you're running 1,000 requests per second continuously, you're paying for 86.4 billion invocations per day. That's roughly $34,560 per day in compute costs alone, before data transfer.
Compare that to a container orchestration approach. An ECS cluster or EKS deployment with reserved instances or savings plans costs a fraction of this. I worked on a SaaS platform handling steady 500 req/s traffic that was paying $15,000 monthly with Lambda. Moving to three t3.xlarge instances on ECS cut that to $1,200 monthly—a 92% reduction.
The break-even point depends on your workload, but generally:
- Sporadic traffic: serverless wins
- Steady traffic above 100 req/s: containers almost always win
- Variable traffic with high peaks: hybrid approach (base on containers, spikes via serverless)
Let me show you the math for a real scenario. Say your Lambda function executes in 200ms on average:
# Lambda cost calculation
Invocations per month: 1000 req/s × 86,400 seconds/day × 30 days = 2.592 billion
Compute time: 2.592B × 0.2s = 518.4M seconds = 8.64M GB-seconds
Cost: 8.64M GB-seconds × $0.0000166667 = $144/month (compute)
Invocation cost: 2.592B × $0.0000002 = $518/month
Total: ~$662/month for compute + invocations
# ECS on-demand (single t3.xlarge)
t3.xlarge: $0.1664/hour × 730 hours = $121.47/month
And that's just one small container. Your actual ECS cluster would likely be 2-3 instances for redundancy, still well under Lambda costs at this throughput level.
Long-running or stateful workloads
Lambda has a 15-minute timeout. If your workload runs longer—report generation, video transcoding, complex data processing—you're either not using Lambda or you're orchestrating multiple functions with state management, which defeats the simplicity advantage.
I worked on a batch processing system for a trading platform that ingested daily market data, performed statistical analysis, and generated overnight reports. The entire pipeline took 25-40 minutes. Lambda simply wasn't viable. We could have used Step Functions to orchestrate multiple Lambdas, but that added complexity, state management overhead, and cost.
The cleaner solution: containerized workers on ECS with Fargate or a Kubernetes job. One container, one unified codebase, clear execution boundaries.
# ECS task definition for long-running batch job
{
"family": "market-analysis",
"containerDefinitions": [
{
"name": "processor",
"image": "market-processor:latest",
"memory": 2048,
"cpu": 1024,
"environment": [
{"name": "DATA_BUCKET", "value": "s3://market-data"}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/market-analysis",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
Another aspect of statefulness: maintaining connections across requests. If your workload needs persistent database connections, WebSocket connections, or in-memory state between invocations, serverless forces you into architectural patterns like connection pooling layers or external state stores. That's extra infrastructure, extra latency, extra cost.
Very low latency requirements
Lambda cold starts are better than they used to be, but they're still unpredictable. A Node.js function might cold start in 50-100ms, a Python function in 100-200ms, a Java function in 500ms+. If your SLA requires sub-100ms response times consistently, cold starts become a production headache.
I built a real-time ride-hailing matching system where driver-to-passenger assignments had a hard 200ms latency budget. Using Lambda would have meant provisioned concurrency on every function, which eliminated cost savings. A containerized solution with proper autoscaling gave us predictable latency without that overhead.
Even with provisioned concurrency, you're paying for idle capacity anyway—the entire cost advantage of serverless disappears. At that point, you might as well use containers with reserved capacity, which gives you more control.
Consider also cold starts at scale. During a traffic spike, AWS spins up many new function instances. If you need 100 concurrent executions across new instances, you might get 20-30 cold starts hitting your users during that spike. With containers at their warm baseline capacity, scaling is much smoother.
Heavy connection pooling to relational databases
This is the killer for serverless + RDS. Each Lambda invocation gets its own execution context. You might have 1,000 concurrent Lambdas, each trying to connect to your RDS instance. Even with connection pooling libraries like Knex or SQLAlchemy, you end up hitting database connection limits or creating hundreds of short-lived connections that thrash your database.
Traditional databases can handle maybe 100-200 concurrent connections comfortably. Serverless architectures often have thousands of concurrent functions. The solution is usually RDS Proxy, which adds another layer and cost.
I worked on a CRM platform handling customer data. Lambda functions queried PostgreSQL heavily—user lookups, transaction history, relationship queries. We started with Lambda + RDS Proxy, but the RDS Proxy bill alone was $2,000+ monthly, and queries were hitting latency issues due to connection pooling overhead.
We moved to ECS with a small pool of durable containers, each with a persistent connection pool. Each container maintained 10-20 database connections. Three containers meant 50 database connections total—well within RDS comfort zones, and much faster.
# Container-based connection pool (Node.js example)
const pool = new Pool({
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST,
port: 5432,
database: process.env.DB_NAME,
max: 20, // Connection pool size per container
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Pool is reused across all requests to this container
app.get('/user/:id', async (req, res) => {
const client = await pool.connect();
try {
const result = await client.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
res.json(result.rows[0]);
} finally {
client.release();
}
});
The database connection issue compounds with microservices. Each service's Lambda fleet tries to maintain its own connection pool. You end up needing connection pooling at multiple layers, which adds latency and complexity.
Common mistakes
- Ignoring the full cost picture: Many teams count only Lambda invocation costs and forget about API Gateway, DynamoDB, data transfer, and especially RDS Proxy. I've seen projects that appeared serverless but were spending 60% of their budget on supporting infrastructure.
- Cold start tuning as a solution: Provisioned concurrency or Lambda@Edge tweaks can reduce cold starts, but once you're paying for idle capacity, the financial argument for serverless evaporates. You're paying for always-on without the transparency of a container.
- Over-engineering with Step Functions: I've seen teams use Step Functions to orchestrate 10+ Lambda functions for workflows that would be a single, readable 30-line function in a container. The complexity isn't worth it unless you're genuinely benefiting from serverless's scaling characteristics.
- Database architectural mismatches: Choosing Lambda then discovering RDS doesn't scale with it, then bolting on RDS Proxy. Design your infrastructure around your database constraints first, not last.
Key takeaways
- Serverless excels at unpredictable, bursty traffic. For steady-state workloads, containers are cheaper and simpler.
- If your function runs longer than a few minutes or needs persistent state, containers are a better fit architecturally.
- Sub-100ms latency SLAs are hard with Lambda cold starts. Provisioned concurrency eliminates the cost advantage anyway.
- Heavy database workloads need connection pooling at the application level, not the function level. Containers give you persistent pools; serverless forces external layers like RDS Proxy.
- Calculate total cost of ownership, not just Lambda invocation costs. Include all supporting services, and compare apples-to-apples with container alternatives.
- A hybrid approach often works best: serverless for event-driven tasks and background jobs, containers for your core API and stateful services.






Comments
Loading comments…