Serverless to Cron: Why We Replaced 97 Lambda Functions with a Few Python Scripts
The Win
Everything our 97 Lambda functions used to do now runs as a handful of Python scripts on a schedule, on servers we own. The SQS queues are gone and so are the SNS topics. When something breaks, there's one log file to read, not a chain of request IDs across five services. And the bill dropped from about $4,500 a month in our peak cloud year to about $100 a month in power and internet (our estimate, not an audit).
Twenty-four of those functions were the same code with a different time window. They're now one script with a loop.
That's the win. Here's the full story, including what we got wrong on both sides.
The Serverless Dream (2016–2022)
We went all-in on serverless. Not halfway. All in.
At peak, two products ran on it: Buzzwords, a news analysis platform, and RoboTrader, a trading automation platform. Together they had 97 Lambda functions, 22 S3 buckets, 8 CloudFront distributions, 16 API Gateway APIs, 6 Elastic Beanstalk environments, 11 SQS queues and 15 SNS topics. In 2019 the account invoiced $53,674.
Buzzwords started as a Drupal site on Elastic Beanstalk with a Lambda backend. The idea was simple: scrape news headlines, pull out trending keywords, rank them by channel, and serve the results through an API. There was even an Alexa skill ("Alexa, what are today's buzzwords?") because it was 2017 and that felt like the future.
The architecture was textbook event-driven:
- Lambda functions scraped and parsed headlines
- SQS queues decoupled the processing stages
- DynamoDB stored the results
- API Gateway served them to the frontend
- CloudFront cached everything
On paper, it was elegant. In practice, it worked. For a while.
RoboTrader followed the same pattern from 2020, at a bigger scale. A few per-symbol trading functions grew into a full CloudFormation-deployed platform. RoboTrader ended up with 65 Lambda functions, 46 of them in its main stack, including 24 time-delta functions covering windows from 1 minute to 10 weeks.
Yes, 24 separate Lambda functions to measure price changes over different time windows. We'll come back to that.
Where It Started to Break Down
Version Sprawl
Our 97 functions ran on four Python runtimes: 2.7, 3.6, 3.7 and 3.8. The oldest, buzzword-today, was written in Python 2.7 in August 2016, and it was still deployed when we started migrating. AWS had been sending deprecation notices for years.
Nobody wants to volunteer to keep 97 functions current across four runtimes, and nobody did. Functions piled up like sediment, layer after layer of "we'll update that later."
The 24-Function Mistake
RoboTrader's stack had 24 functions named BECryptoPriceTimeDelta plus a time window: 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour, and on up to 10 weeks.
They were the same function with a different parameter. The only reason there were 24 is that copying a CloudFormation resource and changing the suffix was easy. Easy to create, painful to maintain.
When a bug turned up in the price-delta logic, it had to be fixed in 24 places. At least, it was supposed to be. We don't know how many copies actually got the fix. We stopped checking.
Queue Sprawl
Buzzwords had 11 SQS queues. Some made sense, separating headline ingestion from keyword calculation from channel aggregation. Others were leftovers from debugging sessions that quietly became permanent infrastructure. We had FIFO queues, dead-letter queues, "ignite" cleanup queues, and queues whose purpose was unclear even to the person who created them.
Each queue had CloudWatch alarms. Each alarm had an SNS topic, and each topic had subscriptions. The observability map for what was really a simple data pipeline looked like a conspiracy theorist's wall of string.
The Bill
The old version of this post said Lambda was a rounding error. The invoices say otherwise.
For most months, Lambda execution really was cheap. Then came December 2019. That month the Lambda line was $20,759, which was all of 2019's Lambda spend in a single month, on a total invoice of $24,473. AWS later issued an $11,079 credit against it. Over its lifetime, Lambda cost us at least $33,137.
The rest of the bill was everything Lambda dragged along. Lifetime figures, gross:
- DynamoDB: $16,100, because every function reads and writes somewhere
- CloudWatch: $11,720, because 97 functions each emit logs and metrics
- WAF: $6,364, because API Gateway belongs behind a firewall
- Business Support plan: $21,399, paid every year from 2017 to 2025, because production workloads on AWS kind of need support
Serverless pricing is fine until a workload scales the way you designed it to. Then you find out what "pay per request" means.
The Migration (2020–2022)
We didn't wake up one morning and decide to leave. It happened gradually. RoboTrader's trading logic moved first, because the trading pipeline needed to sit closer to the execution layer.
The migration looked like this:
| AWS Service | What Replaced It |
|---|---|
| DynamoDB | CouchDB on our own hardware |
| Lambda trading functions | Python scripts + cron |
| RDS MySQL | Self-hosted MySQL |
| API Gateway | Drupal REST APIs behind our own reverse proxy |
| CloudFront | TLS and caching at our own edge |
| Elastic Beanstalk | Apache on our own servers |
| S3 static assets | Local disk + Drupal media |
The CouchDB move was its own adventure (that's the previous post), but the Lambda-to-cron migration was surprisingly anticlimactic. The 24 time-delta functions became one Python script with a loop. The SQS-driven headline pipeline became a scheduled job.
Most of the Lambda logic mapped cleanly to "run this script on a schedule," which is exactly what cron has done since the 1970s.
What We Gained
Debuggability. When a cron job fails, there's a log file on disk. No paging through CloudWatch log groups, no short retention windows, no matching Lambda request IDs against API Gateway request IDs against SQS message IDs. tail -f the log and you're done.
Simplicity. 97 Lambda functions became a handful of Python scripts. 11 SQS queues became function calls in sequence. The whole system fits in one person's head, which is exactly how many people we have.
Cost. We went from about $4,500 a month in 2019 to about $100 a month in power and internet. The hardware was already owned.
Control. No more forced runtime deprecations, no region outages hitting our workloads, no surprise pricing changes, and no "this function was throttled because another one in the same account hit the concurrency limit."
What We Lost
We should be honest here, because serverless does things cron doesn't.
Auto-scaling. Lambda scales to thousands of concurrent executions. Cron runs one instance at a time. For our workloads, a few dozen scheduled tasks on moderate data, that doesn't matter. If we were handling millions of events a second, we'd still be on Lambda.
Managed infrastructure. Nobody patches a Lambda runtime. On our own servers, somebody has to run apt upgrade. That somebody is currently an AI agent, which brings its own problems. We've had outages when the agents themselves went down.
Deployment isolation. Lambda functions are isolated by default. Our Python scripts share a runtime, dependencies and a filesystem. One bad pip install can break everything. Virtual environments help, but it's still a step backward in isolation.
Geographic redundancy. AWS runs across availability zones and regions. Our hardware sits in one place. If the power or the internet connection drops, everything goes down. This has happened, and we don't have a great answer for it yet.
The Ugly Parts
We ran dormant AWS resources for years after the migration: idle Lambda functions, CloudFront distributions enabled but serving nothing, Elastic Beanstalk environments in "Grey" state, DynamoDB tables nobody read. The infrastructure behind the Alexa skill kept billing for about three years after anyone used it.
Why? Shutting things down across an AWS Organization with 11 accounts means someone has to go through every account, service and region and confirm nothing still needs it. For a company our size (one human and a team of AI agents), that's a real project.
The invoices show the tail. We paid AWS $5,776 in 2025, years after the work had moved home. The first two months of 2026 came to $336. After more than a decade and about $244K net, we're still working toward zero.
The other ugly truth: some of the Buzzwords Lambda code, like the keyword engine and the headline pipeline, was genuinely well designed. The event-driven SQS/SNS chain was the right pattern for that workload. We simplified it into cron because we didn't have the team to maintain the complex version, not because the complex version was wrong. A bigger team should have stayed on Lambda.
The Lesson
Serverless is a team-size decision, not a technology decision.
For a small team, or a solo founder with AI agents, managing 97 Lambda functions across 11 accounts costs more effort than running cron on servers you own. "You don't manage servers" is only worth something if what replaces the servers is simpler. For us, it wasn't.
For a team of ten with dedicated DevOps, the math flips. You want isolation, auto-scaling, managed runtimes and deployment pipelines. You don't want one person's bad cron job taking down the trading platform.
We didn't leave Lambda because Lambda is bad. We left because Lambda was built for a team we don't have. We're bootstrapped, running on owned hardware, with AI agents handling operations. Cron fits that reality. Lambda fit the reality we hoped for in 2017.
If your team is growing, stay on Lambda. If your team is you and some AI agents, crontab -e may be the more honest choice.
The Numbers
| Metric | AWS Era (Peak) | On-Prem (Current) |
|---|---|---|
| Lambda functions | 97 | 0 |
| Python scripts (cron) | 0 | A handful |
| SQS queues | 11 | 0 |
| SNS topics | 15 | 0 |
| API Gateway APIs | 16 | 0 |
| Peak-year spend | $53,674 invoiced (2019) | About $100 a month (estimate) |
| Average monthly | About $4,500 (2019) | About $100 |
| Worst single month | $24,473 (Dec 2019), $20,759 of it Lambda | n/a |
| Python runtimes to maintain | 4 (2.7, 3.6, 3.7, 3.8) | 1 |
Want the same result for your cloud bill?
We cut our own AWS bill from $53,674 a year (2019, as billed) to about $100 a month, and we kept every product. That work is led by an AWS Certified Solutions Architect – Professional who holds six AWS certifications, including DevOps Engineer – Professional and Security – Specialty. Because we built these platforms on AWS first, we know what belongs in the cloud, what to bring onto your own hardware, and how to move it without breaking anything. Send us your bill and we'll show you where the money goes.
Get a cloud cost review · See our services
Further reading: Leaving AWS: 97 functions, our own hardware on god of bots · $244K Later: Why We Left AWS for Our Own Hardware · About $4,500 a Month to About $100 · DynamoDB to CouchDB: Why We Left NoSQL for NoSQL · The Buzzwords revival, where the old Lambda engine came back as scripts on our own hardware





Add new comment
Comments
Comments appear after review.