Skip to content
What breaks when you run 1 million Lambda functions in production

What breaks when you run 1 million Lambda functions in production

6 min read serverless

Running a massive serverless multi-account SaaS reveals edge cases you won't find in the docs. This breakdown highlights the realities of true scale-to-zero, rigorous quota management, and why proactive AWS support is mandatory when scaling past a million functions....

Subscribe to listen
audio-thumbnail
What breaks when you run 1 million Lambda functions in production
0:00
/0
Clinical Summary
Diagnosis

Scaling a one-account-per-tenant AWS architecture across millions of isolated Lambda functions shatters the scale-to-zero cost model, driven by exponential idle charges from empty SQS polling and cross-account observability taxes.

Prescription
  • Eliminate Idle Buffers: Remove SQS queues between EventBridge and Lambda to halt continuous empty API polling charges.
  • Centralize Failures: Route all tenant drops into a single Dead Letter Queue instead of polling individual DLQs in every isolated account.
  • Enforce Jitter: Apply randomized offsets to scheduled cron triggers to prevent identical stacks from launching a synchronized self-DDoS attack on your APIs.
Side Effects

Centralizing error handling to save costs physically bridges the strict multi-tenant isolation boundaries that originally justified adopting a complex per-tenant account model.

Script

Architectural best practices come with an expiration date. That date is always tied to your scale.

Picture getting your monthly AWS bill for what you think is a perfectly designed, scale-to-zero serverless architecture. Your customer activity was low this month. You expect your compute costs to match that drop in traffic. But instead, you open the invoice and discover your biggest expense isn't compute, storage, or bandwidth. Your biggest expense is thousands of empty message queues, continuously asking the system, "any work to do?" and racking up API charges every single time they ask.

This happens when you take a standard AWS playbook and multiply it across an extreme infrastructure model. An engineering team recently documented what breaks when you scale a serverless SaaS platform to over one million isolated Lambda functions. They reached this scale by adopting a strict one-AWS-account-per-tenant architecture. Every new customer gets their own fully isolated AWS account. Inside that account, a baseline of twenty microservices is deployed. These microservices consist of DynamoDB tables, EventBridge rules, Step Functions, and anywhere from five to fifteen Lambda functions each.

Decoupling: When the Safety Net Becomes a Trap

We need to look closely at why this breaks, starting with one of the most unquestioned rules in serverless design: decoupling. If you want resilience, the standard advice is to put an Amazon SQS queue between Amazon EventBridge and your Lambda functions. If the downstream function fails or a database gets throttled, the queue holds the message. It's a bulletproof pattern for single-account workloads.

But why does using standard SQS queues for EventBridge decoupling completely ruin the scale-to-zero cost model in a multi-account setup? Because in the AWS billing model, idle doesn't mean free.

To consume events, the Lambda service constantly polls that SQS queue using ReceiveMessage API calls. Even when there are zero messages flowing through the system, the long-polling requests continue. At single-digit or double-digit account scale, this empty polling costs literal pennies. You don't even notice it.

But when you deploy this exact architecture across thousands of isolated tenant accounts, the math turns hostile. That continuous, empty polling aggregates into a massive, exponential cost driver. The team realized that true scale-to-zero is a fiction if you're paying an idle tax on thousands of queues just to maintain a buffer.

To stop the bleeding, they made a hard pivot. They removed SQS entirely from the path between EventBridge and Lambda. They stripped away the safety buffer. Instead of a queue, they rely on aggressive metric monitoring—specifically tracking AsyncEventsDropped and ConcurrentExecutions—to ensure they stay within quotas and don't lose events.

Then they had to handle the failures. Polling individual Dead Letter Queues in every single tenant account just recreated the exact same empty-polling cost problem they had just solved. Their solution was to route all failures across all accounts into a single, centralized Dead Letter Queue.

We need to pause and look at the trade-off here. The entire justification for building a one-AWS-account-per-tenant architecture is strict security boundaries and quota isolation. By routing tenant failures to a centralized queue to save money, you intentionally bridge the very multi-tenant isolation boundary you went through hell to build. You end up treating the AWS account ID as a logical tenant ID inside a shared infrastructure piece. The isolation is no longer strictly physical.

Scheduled Jobs: The Self-Inflicted DDoS

There's another standard practice that turns dangerous at this scale: scheduled jobs. How do thousands of isolated Lambda functions accidentally orchestrate a massive self-DDoS attack against your own internal APIs? It comes down to a tiny, innocuous line of infrastructure code.

Imagine a standard cron trigger. You set a Lambda function to run on a rate of five minutes. In a single account, this is completely fine. The function wakes up, checks a database, does some cleanup, and goes back to sleep.

But remember the architecture. That exact CloudFormation template is duplicated across thousands of tenant accounts. When you use a generic five-minute rate expression, AWS aligns that schedule to the top of the minute. So, when the clock strikes, thousands of Lambda functions across thousands of isolated accounts wake up at the exact same millisecond.

Your internal APIs and third-party services go from zero traffic to thousands of concurrent requests instantly. The resulting spike perfectly mimics a coordinated Distributed Denial of Service attack. You are taking your own platform down simply because every tenant is running the exact same code on the exact same clock.

The fix for this is mandatory jitter. You can never do the same thing at the same time everywhere. The team had to build a standardized internal request scattering library. It enforces randomized batch offsets and staggered updates across all scheduled functions. If you deploy identical stacks across thousands of environments, you have to introduce artificial entropy into their timing.

The Hidden Tax of Observability

The costs of isolation extend beyond compute. Multi-account observability becomes a severe financial penalty. Initially, the team forwarded CloudWatch logs and metrics cross-account to a centralized dashboard. At roughly three dollars per account per month, the cost felt insignificant. But at thousands of accounts, the billing dynamic flips.

The team discovered that forwarding all observability data almost doubled their entire cloud bill. The monitoring tax was higher than the infrastructure it was watching. They had to implement aggressive priority-based filtering just to bring observability costs down to seventy cents per account.

Is the Account-Per-Tenant Model a Practical Idea?

All of this brings us to a fundamental question for anyone designing a new system. Is the one-AWS-account-per-tenant model actually a practical idea, or just an extreme edge case?

Make no mistake, this is an extreme edge case. Account-per-tenant isolation is the nuclear option for SaaS architecture. You only reach for this when enterprise customers demand hard, physical data separation or distinct, non-negotiable compliance boundaries. For ninety-five percent of business-to-business software companies, this model is absolute overkill.

The operational toll of this architecture is immense. New tenant accounts might go from request to ready in under fifteen minutes using AWS Organizations and Step Functions, but that's the easy part. The pain is maintaining them. Native tools like AWS CloudFormation StackSets, which are explicitly designed for multi-account deployments, fail at this scale. The team hit a performance ceiling where StackSets produced errors that compounded into significant operational blockers.

When your native multi-account deployment tool breaks, you're suddenly in the business of building and maintaining custom deployment engines just to push updates to your own software. And you have to consider what's missing from the discussion. Coordinating database schema migrations across thousands of isolated tenant accounts without downtime is a famously difficult distributed systems problem. And the baseline infrastructure cost of duplicating twenty microservices—meaning dozens of idle Lambda functions and DynamoDB tables per tenant—introduces a permanent baseline footprint. Even if you optimize the idle cost for inactive accounts to less than one dollar a month, your aggregate cloud bill is rigidly tied to the number of tenants, not just their actual usage.

The Danger of the Scale Fallacy

This highlights the danger of the scale fallacy. It's incredibly tempting to read a postmortem about running one million Lambda functions and assume your team needs to adopt these patterns today to future-proof your product. You don't.

Best practices are highly contextual. An architecture that solves a constraint at the extreme edge will likely bankrupt or stall a smaller team trying to find product-market fit.

  • Don't rip out your SQS queues. For standard multi-tenant applications, using a queue to buffer EventBridge to Lambda is exactly what you should do. The polling cost is irrelevant until you multiply it by thousands of accounts.
  • Don't adopt per-tenant AWS accounts unless your sales team is losing massive enterprise contracts because you lack physical data isolation. Use a pooled multi-tenant architecture. Share your compute, share your databases, and enforce isolation logically using tenant IDs at the application or row level.

This keeps your cloud bill tightly coupled to actual usage, rather than paying an idle infrastructure tax. It keeps you out of the business of building custom CloudFormation orchestrators.

Scale-to-zero is a powerful concept, but at a certain volume, zero is always almost-zero. The baseline monitoring tax sets a floor on your cloud bill. Acknowledge that floor. Build for the scale you have right now, and the scale you are about to reach next. Save the nuclear options for when you actually hit the wall.

This is TAKEYOURPILLS.TECH. Go ship something.

References

/