Imagine waiting over 50 minutes for every pre- and post-merge check on your code. Sounds painful, right? Now imagine running through this grind 200+ times a week. That was our reality until we, the Test and Release Infrastructure (TRI) team, decided enough was enough.
Fast forward to today: those 50-minute waits? Gone. Android and iOS build times now clock in at under 20 minutes. Here’s the inside scoop on how we made this magic happen—and yes, it involved some fun experimentation and a bit of sweat (but thankfully, no tears).
What worked (a.k.a., our secret sauce)
1. Newer, shinier machines
Let’s face it: the old AWS instance types running our builds were like trying to watch high-definition videos on a dial-up connection. Upgrading them was an obvious first step.
- For Android: We tested 16 instance types and learned the hard way that throwing more CPU power at a JVM doesn’t help—it’s a memory hog! The r7a instances, with their ample memory and reliable availability, turned out to be perfect. Result? A 21% improvement in build times.Oh, and pro tip: Fast disk I/O (like NVMe SSDs) speeds up Android builds even more. That’s next on our to-do list.
- For iOS: Apple Silicon M2 Macs are much more efficient when compared to the old x86-based mac1.metal instances. By switching to EC2 mac2-m2.metal machines, we slashed build times by a whopping 60%. Developers were able to immediately notice the improvement here.
And yes, we made peace with slightly higher costs. Newer machines are pricier, but quicker builds made them worth every penny.
2. More parallelization, less bottleneck drama
CI pipelines are like crowded intersections. Too much happening in one place? Total traffic jam. We revamped our pipelines to keep tasks moving smoothly:
- For Android: We rearranged and optimized tasks, reducing serial execution time by 20%. This was key to balancing our spending on those shiny new machines.
- For iOS: We killed long startup delays by ditching Jenkins Pipelines for freestyle jobs. Startup times shrank, and our developers got their builds faster. (Bonus: we built a nifty Python library to maintain some of the syntactic sugar we loved from Pipelines!)
3. Caching is king
Why reinvent the wheel—or, in this case, rebuild unchanged code?
- Dependency caching: Cached dependencies meant fewer downloads and less network latency. For iOS, we cracked the code on SwiftPM’s built-in caching, shaving ~4 minutes off build times.
- Task caching: For Android, we tapped into Gradle’s remote S3 cache. This avoided redundant compilations and reused outputs whenever possible. Result? 25% faster builds.
4. Toolchain upgrades (a.k.a., spring cleaning)
Sometimes, all you need is an upgrade. We waved goodbye to KAPT (Kotlin annotation processing) and embraced KSP, which is faster and sleeker. Along with a Kotlin bump, this cut Android build times by another 30%.
What didn’t work (but was worth a try)
XCRemoteCache woes
We had high hopes for XCRemoteCache, a tool for iOS remote caching. But after months of head-scratching over cache misses and mysterious build failures, we had to call it quits. Lesson learned? Not every tool fits every setup. Bazel is next on our radar.
Results: the glow-up in numbers and sentiments
Key takeaways (i.e., what we learned)
- Small Wins Matter: Even shaving a few seconds off CI times makes a big difference over hundreds of PRs. Developers notice—and appreciate—these changes.
- Speed + Stability = Happy Developers: Slow, glitchy builds can derail productivity. Fixing outliers and adding retry logic kept things smooth, fast, and frustration-free.
- Reduce Waste, Responsibly: New machines meant higher costs, but smarter resource allocation (hello, Savings Plan and EC2 Autoscaling Groups!) kept things in check.
The bottom line
Improving build times isn’t just about better CI—it’s about creating a better developer experience. And when your developers are happy, everyone wins.
If you want to work at a place that values and optimizes for engineering ease and excellence, we’re hiring!