Optimizing build times with Bazel and Buildfarm

Introduction
Optimizing build times is crucial for maintaining a fast and efficient development pace in continuous integration pipelines.
On a client project, we created a mono-repository that now contains ~40 micro-services, mainly in Go such as APIs, Kafka consumers, Temporal workers, and also 4 front-end projects in React and TypeScript.
Bazel handles everything from building container images to pushing them to a registry and creating Kubernetes deployment manifests. We follow the GitOps philosophy 100%.
To build these projects and deploy them in our CI/CD, we use Bazel, an open-source build tool developed by Google.
It offers powerful features to accelerate this process.
When combined with Buildfarm, a distributed build and cache solution, the performance gains can be significant.
In this article, I'll share some configuration options that we added to our .bazelrc file to achieve faster builds.
How does Bazel work?
Without going into too much detail, Bazel is a build system and dependency management tool that supports multiple programming languages.
It is designed to be fast, reliable, and capable of handling various large-scale projects.
Here are some of its main features:
- Incremental Builds: Bazel only rebuilds the parts of the project that have changed, the rest is cached,
- Parallelization: It executes build actions in parallel to speed up the process,
- Reproducibility: Builds are reproducible, ensuring that the same code produces the same results every time.
What is Buildfarm?
Buildfarm is a distributed cache and build solution that allows you to run Bazel builds across multiple machines.
By distributing build tasks, Buildfarm can significantly reduce the time needed to compile large projects.
It integrates with several tools and particularly well with Bazel.
Optimizing the .bazelrc File
Let's get to the heart of the matter, here are some options I was able to identify among the available options to speed up build times.
In the context of our project, when a minimal change is made, our CI/CD job takes only 3 minutes to rebuild all 40 services.
The .bazelrc file is essential for configuring build options in Bazel. Here are some key options to include to improve build times:
Fast compilation mode
build --compilation_mode=fastbuildUse fastbuild compilation mode to speed up the build process by sacrificing some optimizations.
Reuse sandbox directories
build --reuse_sandbox_directoriesReuse sandbox directories to avoid recreating execution environments for each build.
Disable external repository files check
build --noexperimental_check_external_repository_filesDisable experimental checking of external repository files to speed up the build process.
Disable action cache metadata storage
build --noexperimental_action_cache_store_output_metadataDisable experimental storage of action cache metadata to reduce build time.
Parallel Skyframe analysis execution
build --experimental_merged_skyframe_analysis_executionEnable parallel execution of Skyframe analyses to speed up dependency analysis.
Use distributed Merkle cache
build --experimental_remote_merkle_tree_cacheUse a distributed Merkle cache to efficiently store and retrieve build artifacts.
Use hardlinks for repository cache
build --experimental_repository_cache_hardlinksUse hardlinks for repository cache to reduce disk space usage and speed up file access.
Protection against concurrent changes
build --experimental_guard_against_concurrent_changesProtect against concurrent changes that can interfere with the build process.
Reduce downloads from remote cache
build --remote_download_outputs=minimalReduce the number of files downloaded from the remote cache to speed up builds.
For tests
test --build_tests_onlyBuild only what's necessary for tests, which greatly speeds up when you target the entire project.
Strict action environment and complete test output
test --incompatible_strict_action_env --test_output=allUse a strict action environment and generate complete test output for better traceability and increased performance.
General Best Practices
These options improve builds, but it's important to keep these rules in mind to maintain fast builds:
- Dependency Reduction: Simplify your
BUILDandWORKSPACEfiles to minimize dependencies, - Powerful Machines: Run builds on machines with good CPU and RAM, as Bazel uses a lot of resources,
- Profiling: Use Bazel's profiling tools (
--profile) to identify and resolve bottlenecks.
Conclusion
By properly configuring Bazel and Buildfarm, and optimizing your .bazelrc file, you can significantly reduce build times.
These optimizations allow you to maintain a fast and efficient development pace, even for large-scale projects.
Don't hesitate to experiment and adapt these configurations based on your project's specific needs.