I Rewrote My Backend in Go in 72 Hours and Cut My AWS Bill by 94%
Or how I stopped paying €4,200 a month for infrastructure that was barely doing anything
The morning I almost choked on my coffee
Tuesday, October 3rd, 9:17 AM. I'm sipping my coffee, opening the AWS console like I do every morning. Except this time, I almost spat it all over my keyboard.
€4,287.32.
That was my bill for the month. For an API that handles maybe 2,000 requests per minute at peak hours.
I thought it was a mistake. I checked the metrics. Nothing unusual. The problem was me. For two years, I'd been adding EC2 instances without ever questioning the architecture. A t3.large here, a t3.large there. "It's for redundancy," I told myself. "For traffic spikes."
Except the spikes happen two hours a day. The rest of the time, my 8 instances were sitting at 15% CPU, eating RAM and draining my bank account.
I made a stupid decision: rewrite everything in Go in 72 hours.
I'll probably never do it again. But it was the best technical decision I've ever made.
How I got there in the first place
A quick flashback: back when I thought Node was life
I'll be honest — I'm a fullstack dev at heart. I learned JavaScript, I loved the flexibility, the npm packages, being able to do both frontend and backend with the same tech.
My API started as a tiny Express.js app running on a single t3.micro instance. It worked great. I had 15 users a day, my AWS bill was like 12 bucks.
Then one day, a client said "we need this to scale." And I felt like I'd grown wings. I added instances, load balancers, services. Except... I never removed the ones that weren't doing anything anymore.
The npm dependencies kept piling up in my package.json: 47 at the last count. Some I'd added back in 2021 for a feature I never even shipped. They were still there. Slowing down the boot, eating memory, reminding me of all my bad decisions.
One day I ran npm audit. 32 vulnerabilities, 5 of them critical. I spent 3 hours trying to fix them. I fixed 4. The rest had "breaking changes" everywhere. I gave up.
That's when I started looking elsewhere.
The infrastructure that cost more than rent
Here's what I was paying for at the time:
- 8 EC2 t3.large instances — one for each microservice I'd carved out for "scalability." None of them ever handled more than 50 req/sec.
- An Application Load Balancer — because it looks professional.
- Two NAT Gateways — for redundancy.
- RDS PostgreSQL — the database that would sometimes freeze for 10 seconds and force me to restart the app.
- Logs everywhere — CloudWatch, because "logs are good," except I forgot to set limits.
Total mess. But I was paying top dollar for it.
Why Go saved my ass
I'm not going to give you a lecture. Here are the concrete numbers:
RAM — the real deal
Node.js, on my latest version, was using 1.4 GB of RAM just to boot the app on one instance.
Go? 48 MB. I'm not kidding.
On the first test I ran, I launched a 15 MB Go binary on a t3.micro. The API responded in 20ms. I thought I'd measured wrong. I checked three times.
It wasn't a mistake.
Node's garbage collector does its thing. Go's garbage collector actually works.
Cold starts — or the hell of APIs that wake up slowly
One time I put a critical endpoint in a Node Lambda — just to test. First call, 400ms wait. On an API that normally runs at 20ms, that's just unacceptable.
Go on Lambda: 95ms. Three times faster.
The static binary — the game changer
Go compiles into a single binary. No Node.js needed, no npm install, no modules. You build it, you drop it, you run it. Done.
My deployment pipeline went from 7 minutes to 23 seconds.
On the first deploy, I thought the pipeline had failed — it was just that fast.
The real reason I switched: simplicity
With Node, you write code, make a typo, and the app crashes. Sometimes it doesn't even crash — it keeps running with dangling promises, and you find out three days later.
With Go, you compile. If it compiles, it runs. Period.
Did I have bugs in Go? Sure. But they were logic bugs, not syntax or type errors. In Node, you spend half your life debugging stupid stuff.
The most intense 72 hours of my career
The insane schedule
I swear I did this properly:
- Day -3: Took a day off. Turned off my phone. Told my wife I'd be a zombie.
- Day -2, 8AM-12PM: Drew out all the business logic on paper. No code, just flows.
- Day -2, 1PM-8PM: Picked my libraries (used Fiber because I like the Express-like syntax).
- Day -1: Coded like crazy. 8AM to midnight. Had a first working version at 11:47 PM.
- D-Day, 8AM-2PM: Tests, benchmarks, load testing.
- D-Day, 3PM: Go live.
- D-Day, 6PM: Production was up. Everything was running. My wife gave me a weird look. I was exhausted but thrilled.
The mistakes I made
Because I'd be lying if I said everything was perfect:
- I coded too fast. On go-live day, I realized I'd forgotten transaction handling. Had to add a middleware layer in a hurry.
- Didn't test timeouts thoroughly. Day one, requests that took longer than 5 seconds just crashed. Had to increase timeouts and rework external calls.
- Had a memory leak period. Memory kept climbing and not releasing — classic leak. Turns out it was a misplaced
defer. Fixed it in an hour.
What helped me pull it off
- The Go community — super helpful, fast responses on Reddit.
- The Go playground — perfect for testing snippets on the fly.
- Real coffee beans. No instant coffee during those 72 hours.
Results that made me tear up (with joy)
AWS bill: €4,287 → €252
94% reduction.
How?
- No more heavy instances — switched to t4g.micro (ARM) at €6/month. One single instance now handles all my traffic.
- No more redundant NAT Gateways — shut one down, resized caches.
- No more Load Balancer — with one instance, no need to distribute traffic.
- No more monster logs — keeping one month instead of forever.
Performance: 180ms → 45ms
My main API went from 180ms average latency to 45ms. On good days I've seen it dip to 35ms.
Deployments: 7 minutes → 23 seconds
Build and deploy now take 23 seconds. Simple math: the Go binary is 15MB, compared to 800MB with node_modules.
What I learned (and what you should take away)
1. Scale smart, not hard
Having 8 idle instances isn't scaling — it's waste. Once your code is optimized, one well-configured instance can handle way more than 8 poorly configured ones.
2. Simpler is usually cheaper
I spent hours configuring complex AWS services. Rewriting in Go let me use a dead-simple architecture: one instance, one database, one codebase. No microservices for the sake of microservices. The code just calls routes.
3. Reliability has a price — and Go lowers it
Fewer bugs, fewer restarts, less maintenance. My weekends are peaceful again.
4. My secret weapons
- A whiteboard to sketch everything.
- A well-thought-out Dockerfile.
- Monitoring from day one — Prometheus + Grafana to see load curves in real time.
So, do I regret it?
No. Absolutely not.
I lost 3 days, but I gained €4,000 per month, a faster API, fewer bugs, and a bill that actually makes me smile.
Is Go the answer to everything? No. There are cases where Node or Python are still the right choice.
But for me, right now, my stack is simpler, more solid, and I actually enjoy writing Go.
Want me to take a look at your backend?
If you see yourself in this story — if you're thinking "that's literally me" — hit me up. I'd be happy to take a look at your architecture, no pressure. Maybe a simple optimization (or a little Go) could save you thousands.
✉️ DM me on Twitter or reach out through the contact form on my site.
P.S. This month's bill just came in: €187. I'm at 95%.
P.P.S. I'm still freelancing under my own company. But at least now I sleep well at night.
No comments:
Post a Comment